Skip to content

Commit

Permalink
Merge pull request #1180 from neomantra/nm-1100
Browse files Browse the repository at this point in the history
Add GenericPackagesServices (#1100)
  • Loading branch information
svanharmelen authored Nov 22, 2021
2 parents 8f8fc8c + 260e7db commit 16f298f
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ to add new and/or missing endpoints. Currently the following services are suppor
- [x] Events
- [x] Feature Flags
- [ ] Geo Nodes
- [x] Generic Packages
- [x] GitLab CI Config Templates
- [x] Gitignores Templates
- [x] Group Access Requests
Expand All @@ -57,6 +58,7 @@ to add new and/or missing endpoints. Currently the following services are suppor
- [x] Notes (comments)
- [x] Notification Settings
- [x] Open Source License Templates
- [x] Packages
- [x] Pages
- [x] Pages Domains
- [x] Personal Access Tokens
Expand Down
158 changes: 158 additions & 0 deletions generic_packages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//
// Copyright 2021, Sune Keller
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package gitlab

import (
"bytes"
"fmt"
"io"
"net/http"
"time"
)

// GenericPackagesService handles communication with the packages related
// methods of the GitLab API.
//
// GitLab docs:
// https://docs.gitlab.com/ee/user/packages/generic_packages/index.html
type GenericPackagesService struct {
client *Client
}

// GenericPackagesFile represents a GitLab generic package file.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/user/packages/generic_packages/index.html#publish-a-package-file
type GenericPackagesFile struct {
ID int `json:"id"`
PackageID int `json:"package_id"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
Size int `json:"size"`
FileStore int `json:"file_store"`
FileMD5 string `json:"file_md5"`
FileSHA1 string `json:"file_sha1"`
FileName string `json:"file_name"`
File struct {
URL string `json:"url"`
} `json:"file"`
FileSHA256 string `json:"file_sha256"`
VerificationRetryAt *time.Time `json:"verification_retry_at"`
VerifiedAt *time.Time `json:"verified_at"`
VerificationFailure bool `json:"verification_failure"`
VerificationRetryCount int `json:"verification_retry_count"`
VerificationChecksum string `json:"verification_checksum"`
VerificationState int `json:"verification_state"`
VerificationStartedAt *time.Time `json:"verification_started_at"`
NewFilePath string `json:"new_file_path"`
}

// FormatPackageURL returns the GitLab Package Registry URL for the given artifact metadata, without the BaseURL.
// This does not make a GitLab API request, but rather computes it based on their documentation.
func (s *GenericPackagesService) FormatPackageURL(pid interface{}, packageName, packageVersion, fileName string) (string, error) {
project, err := parseID(pid)
if err != nil {
return "", err
}
u := fmt.Sprintf(
"projects/%s/packages/generic/%s/%s/%s",
pathEscape(project),
pathEscape(packageName),
pathEscape(packageVersion),
pathEscape(fileName),
)
return u, nil
}

// PublishPackageFileOptions represents the available PublishPackageFile()
// options.
//
// GitLab docs:
// https://docs.gitlab.com/ee/user/packages/generic_packages/index.html#publish-a-package-file
type PublishPackageFileOptions struct {
Status *GenericPackageStatusValue `url:"status,omitempty" json:"status,omitempty"`
Select *GenericPackageSelectValue `url:"select,omitempty" json:"select,omitempty"`
}

// PublishPackageFile uploads a file to a project's package registry.
//
// GitLab docs:
// https://docs.gitlab.com/ee/user/packages/generic_packages/index.html#publish-a-package-file
func (s *GenericPackagesService) PublishPackageFile(pid interface{}, packageName, packageVersion, fileName string, content io.Reader, opt *PublishPackageFileOptions, options ...RequestOptionFunc) (*GenericPackagesFile, *Response, error) {
project, err := parseID(pid)
if err != nil {
return nil, nil, err
}
u := fmt.Sprintf(
"projects/%s/packages/generic/%s/%s/%s",
pathEscape(project),
pathEscape(packageName),
pathEscape(packageVersion),
pathEscape(fileName),
)

// We need to create the request as a GET request to make sure the options
// are set correctly. After the request is created we will overwrite both
// the method and the body.
req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
if err != nil {
return nil, nil, err
}

// Overwrite the method and body.
req.Method = http.MethodPut
req.SetBody(content)

f := new(GenericPackagesFile)
resp, err := s.client.Do(req, f)
if err != nil {
return nil, resp, err
}

return f, resp, err
}

// DownloadPackageFile allows you to download the package file.
//
// GitLab docs:
// https://docs.gitlab.com/ee/user/packages/generic_packages/index.html#download-package-file
func (s *GenericPackagesService) DownloadPackageFile(pid interface{}, packageName, packageVersion, fileName string, options ...RequestOptionFunc) ([]byte, *Response, error) {
project, err := parseID(pid)
if err != nil {
return nil, nil, err
}
u := fmt.Sprintf(
"projects/%s/packages/generic/%s/%s/%s",
pathEscape(project),
pathEscape(packageName),
pathEscape(packageVersion),
pathEscape(fileName),
)

req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
if err != nil {
return nil, nil, err
}

var f bytes.Buffer
resp, err := s.client.Do(req, &f)
if err != nil {
return nil, resp, err
}

return f.Bytes(), resp, err
}
66 changes: 66 additions & 0 deletions generic_packages_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// Copyright 2021, Sune Keller
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package gitlab

import (
"fmt"
"net/http"
"reflect"
"strings"
"testing"
)

func TestPublishPackageFile(t *testing.T) {
mux, server, client := setup(t)
defer teardown(server)

mux.HandleFunc("/api/v4/projects/1234/packages/generic/foo/0.1.2/bar-baz.txt", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodPut)
fmt.Fprint(w, `
{
"message": "201 Created"
}
`)
})

_, _, err := client.GenericPackages.PublishPackageFile(1234, "foo", "0.1.2", "bar-baz.txt", strings.NewReader("bar = baz"), &PublishPackageFileOptions{})
if err != nil {
t.Errorf("GenericPackages.PublishPackageFile returned error: %v", err)
}
}

func TestDownloadPackageFile(t *testing.T) {
mux, server, client := setup(t)
defer teardown(server)

mux.HandleFunc("/api/v4/projects/1234/packages/generic/foo/0.1.2/bar-baz.txt", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, strings.TrimSpace(`
bar = baz
`))
})

packageBytes, _, err := client.GenericPackages.DownloadPackageFile(1234, "foo", "0.1.2", "bar-baz.txt")
if err != nil {
t.Errorf("GenericPackages.DownloadPackageFile returned error: %v", err)
}

want := []byte("bar = baz")
if !reflect.DeepEqual(want, packageBytes) {
t.Errorf("GenericPackages.DownloadPackageFile returned %+v, want %+v", packageBytes, want)
}
}
2 changes: 2 additions & 0 deletions gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type Client struct {
ExternalStatusChecks *ExternalStatusChecksService
Features *FeaturesService
FreezePeriods *FreezePeriodsService
GenericPackages *GenericPackagesService
GeoNodes *GeoNodesService
GitIgnoreTemplates *GitIgnoreTemplatesService
GroupBadges *GroupBadgesService
Expand Down Expand Up @@ -312,6 +313,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) {
c.ExternalStatusChecks = &ExternalStatusChecksService{client: c}
c.Features = &FeaturesService{client: c}
c.FreezePeriods = &FreezePeriodsService{client: c}
c.GenericPackages = &GenericPackagesService{client: c}
c.GeoNodes = &GeoNodesService{client: c}
c.GitIgnoreTemplates = &GitIgnoreTemplatesService{client: c}
c.GroupBadges = &GroupBadgesService{client: c}
Expand Down
33 changes: 33 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,39 @@ func FileAction(v FileActionValue) *FileActionValue {
return p
}

// GenericPackageSelectValue represents a generic package select value.
type GenericPackageSelectValue string

// The available generic package select values.
const (
SelectPackageFile GenericPackageSelectValue = "package_file"
)

// GenericPackageSelect is a helper routine that allocates a new
// GenericPackageSelectValue value to store v and returns a pointer to it.
func GenericPackageSelect(v GenericPackageSelectValue) *GenericPackageSelectValue {
p := new(GenericPackageSelectValue)
*p = v
return p
}

// GenericPackageStatusValue represents a generic package status.
type GenericPackageStatusValue string

// The available generic package statuses.
const (
PackageDefault GenericPackageStatusValue = "default"
PackageHidden GenericPackageStatusValue = "hidden"
)

// GenericPackageStatus is a helper routine that allocates a new
// GenericPackageStatusValue value to store v and returns a pointer to it.
func GenericPackageStatus(v GenericPackageStatusValue) *GenericPackageStatusValue {
p := new(GenericPackageStatusValue)
*p = v
return p
}

// ISOTime represents an ISO 8601 formatted date
type ISOTime time.Time

Expand Down

0 comments on commit 16f298f

Please sign in to comment.