From 0fe2ace1324e43151e16df56686dd9fd9eb0f74d Mon Sep 17 00:00:00 2001 From: b398f0fcac Date: Sat, 15 Jul 2023 02:59:55 +0000 Subject: [PATCH] Access the details of packages (#620) This pull request provides access to the packages API by allowing packages to be listed, retrieved individually and for the files within a package to be listed. Resolves gitea/go-sdk#619. Co-authored-by: root Co-authored-by: John Olheiser Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/620 Co-authored-by: b398f0fcac Co-committed-by: b398f0fcac --- gitea/org_team.go | 2 + gitea/package.go | 93 ++++++++++++++++++++++++++++++++++++ gitea/package_test.go | 108 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 gitea/package.go create mode 100644 gitea/package_test.go diff --git a/gitea/org_team.go b/gitea/org_team.go index e4de31e..e7caa5a 100644 --- a/gitea/org_team.go +++ b/gitea/org_team.go @@ -43,6 +43,8 @@ const ( RepoUnitReleases RepoUnitType = "repo.releases" // RepoUnitProjects represent projects of a repository RepoUnitProjects RepoUnitType = "repo.projects" + // RepoUnitPackages represents packages of a repository + RepoUnitPackages RepoUnitType = "repo.packages" ) // ListTeamsOptions options for listing teams diff --git a/gitea/package.go b/gitea/package.go new file mode 100644 index 0000000..df3bd3c --- /dev/null +++ b/gitea/package.go @@ -0,0 +1,93 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "time" +) + +// Package represents a package +type Package struct { + // the package's id + ID int64 `json:"id"` + // the package's owner + Owner User `json:"owner"` + // the repo this package belongs to (if any) + Repository *string `json:"repository"` + // the package's creator + Creator User `json:"creator"` + // the type of package: + Type string `json:"type"` + // the name of the package + Name string `json:"name"` + // the version of the package + Version string `json:"version"` + // the date the package was uploaded + CreatedAt time.Time `json:"created_at"` +} + +// PackageFile represents a file from a package +type PackageFile struct { + // the file's ID + ID int64 `json:"id"` + // the size of the file in bytes + Size int64 `json:"size"` + // the name of the file + Name string `json:"name"` + // the md5 hash of the file + MD5 string `json:"md5"` + // the sha1 hash of the file + SHA1 string `json:"sha1"` + // the sha256 hash of the file + SHA256 string `json:"sha256"` + // the sha512 hash of the file + SHA512 string `json:"sha512"` +} + +// ListPackagesOptions options for listing packages +type ListPackagesOptions struct { + ListOptions +} + +// ListPackages lists all the packages owned by a given owner (user, organisation) +func (c *Client) ListPackages(owner string, opt ListPackagesOptions) ([]*Package, *Response, error) { + if err := escapeValidatePathSegments(&owner); err != nil { + return nil, nil, err + } + opt.setDefaults() + packages := make([]*Package, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s?%s", owner, opt.getURLQuery().Encode()), nil, nil, &packages) + return packages, resp, err +} + +// GetPackage gets the details of a specific package version +func (c *Client) GetPackage(owner, packageType, name, version string) (*Package, *Response, error) { + if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil { + return nil, nil, err + } + foundPackage := new(Package) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil, foundPackage) + return foundPackage, resp, err +} + +// DeletePackage deletes a specific package version +func (c *Client) DeletePackage(owner, packageType, name, version string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil) + return resp, err +} + +// ListPackageFiles lists the files within a package +func (c *Client) ListPackageFiles(owner, packageType, name, version string) ([]*PackageFile, *Response, error) { + if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil { + return nil, nil, err + } + packageFiles := make([]*PackageFile, 0) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s/files", owner, packageType, name, version), nil, nil, &packageFiles) + return packageFiles, resp, err +} diff --git a/gitea/package_test.go b/gitea/package_test.go new file mode 100644 index 0000000..91c21f0 --- /dev/null +++ b/gitea/package_test.go @@ -0,0 +1,108 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "fmt" + "log" + "net/http" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +// create an org with a single package for testing purposes +func createTestPackage(t *testing.T, c *Client) error { + _, _ = c.DeletePackage("PackageOrg", "generic", "MyPackage", "v1") + _, _ = c.DeleteOrg("PackageOrg") + _, _, _ = c.CreateOrg(CreateOrgOption{Name: "PackageOrg"}) + + client := &http.Client{ + Timeout: time.Second * 10, + } + + reader := bytes.NewReader([]byte("Hello world!")) + + url := fmt.Sprintf("%s/api/packages/PackageOrg/generic/MyPackage/v1/file1.txt", os.Getenv("GITEA_SDK_TEST_URL")) + req, err := http.NewRequest(http.MethodPut, url, reader) + if err != nil { + log.Println(err) + return err + } + + req.SetBasicAuth(os.Getenv("GITEA_SDK_TEST_USERNAME"), os.Getenv("GITEA_SDK_TEST_PASSWORD")) + response, err := client.Do(req) + if err != nil { + return err + } + defer response.Body.Close() + + return nil +} + +func TestListPackages(t *testing.T) { + log.Println("== TestListPackages ==") + c := newTestClient() + err := createTestPackage(t, c) + assert.NoError(t, err) + + packagesList, _, err := c.ListPackages("PackageOrg", ListPackagesOptions{ + ListOptions{ + Page: 1, + PageSize: 1000, + }, + }) + assert.NoError(t, err) + assert.Len(t, packagesList, 1) +} + +func TestGetPackage(t *testing.T) { + log.Println("== TestGetPackage ==") + c := newTestClient() + err := createTestPackage(t, c) + assert.NoError(t, err) + + pkg, _, err := c.GetPackage("PackageOrg", "generic", "MyPackage", "v1") + assert.NoError(t, err) + assert.NotNil(t, pkg) + assert.True(t, pkg.Name == "MyPackage") + assert.True(t, pkg.Version == "v1") + assert.NotEmpty(t, pkg.CreatedAt) +} + +func TestDeletePackage(t *testing.T) { + log.Println("== TestDeletePackage ==") + c := newTestClient() + err := createTestPackage(t, c) + assert.NoError(t, err) + + _, err = c.DeletePackage("PackageOrg", "generic", "MyPackage", "v1") + assert.NoError(t, err) + + // no packages should be listed following deletion + packagesList, _, err := c.ListPackages("PackageOrg", ListPackagesOptions{ + ListOptions{ + Page: 1, + PageSize: 1000, + }, + }) + assert.NoError(t, err) + assert.Len(t, packagesList, 0) +} + +func TestListPackageFiles(t *testing.T) { + log.Println("== TestListPackageFiles ==") + c := newTestClient() + err := createTestPackage(t, c) + assert.NoError(t, err) + + packageFiles, _, err := c.ListPackageFiles("PackageOrg", "generic", "MyPackage", "v1") + assert.NoError(t, err) + assert.Len(t, packageFiles, 1) + assert.True(t, packageFiles[0].Name == "file1.txt") +}