mirror of
https://github.com/aljazceru/kata-containers.git
synced 2025-12-26 10:34:24 +01:00
Add kata-pkgsync as the OBS to Packagecloud sync tool. Fixes: #506 Signed-off-by: Marco Vedovati <mvedovati@suse.com>
242 lines
6.2 KiB
Go
242 lines
6.2 KiB
Go
// Package pkgcloud allows you to talk to the packagecloud API.
|
|
// See https://packagecloud.io/docs/api
|
|
package pkgcloud
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
|
|
"github.com/mlafeldt/pkgcloud/upload"
|
|
"github.com/peterhellberg/link"
|
|
pb "gopkg.in/cheggaaa/pb.v1"
|
|
)
|
|
|
|
//go:generate bash -c "./gendistros.py supportedDistros | gofmt > distros.go"
|
|
|
|
// ServiceURL is the URL of packagecloud's API.
|
|
const ServiceURL = "https://packagecloud.io/api/v1"
|
|
|
|
const UserAgent = "pkgcloud Go client"
|
|
|
|
// A Client is a packagecloud client.
|
|
type Client struct {
|
|
token string
|
|
progressBar bool
|
|
}
|
|
|
|
// NewClient creates a packagecloud client. API requests are authenticated
|
|
// using an API token. If no token is passed, it will be read from the
|
|
// PACKAGECLOUD_TOKEN environment variable.
|
|
func NewClient(token string) (*Client, error) {
|
|
if token == "" {
|
|
token = os.Getenv("PACKAGECLOUD_TOKEN")
|
|
if token == "" {
|
|
return nil, errors.New("PACKAGECLOUD_TOKEN unset")
|
|
}
|
|
}
|
|
return &Client{token, false}, nil
|
|
}
|
|
|
|
// Print a progress bar of paginated API requests when show is set to true
|
|
func (c *Client) ShowProgress(show bool) {
|
|
c.progressBar = show
|
|
}
|
|
|
|
// decodeResponse checks http status code and tries to decode json body
|
|
func decodeResponse(resp *http.Response, respJson interface{}) error {
|
|
switch resp.StatusCode {
|
|
case http.StatusOK, http.StatusCreated:
|
|
return json.NewDecoder(resp.Body).Decode(respJson)
|
|
case http.StatusUnauthorized, http.StatusNotFound:
|
|
return fmt.Errorf("HTTP status: %s", http.StatusText(resp.StatusCode))
|
|
case 422: // Unprocessable Entity
|
|
var v map[string][]string
|
|
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
|
|
return err
|
|
}
|
|
for _, messages := range v {
|
|
for _, msg := range messages {
|
|
// Only return the very first error message
|
|
return errors.New(msg)
|
|
}
|
|
break
|
|
}
|
|
return fmt.Errorf("invalid HTTP body")
|
|
default:
|
|
return fmt.Errorf("unexpected HTTP status: %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
// CreatePackage pushes a new package to packagecloud.
|
|
func (c Client) CreatePackage(repo, distro, pkgFile string) error {
|
|
var extraParams map[string]string
|
|
if distro != "" {
|
|
distID, ok := supportedDistros[distro]
|
|
if !ok {
|
|
return fmt.Errorf("invalid distro name: %s", distro)
|
|
}
|
|
extraParams = map[string]string{
|
|
"package[distro_version_id]": strconv.Itoa(distID),
|
|
}
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("%s/repos/%s/packages.json", ServiceURL, repo)
|
|
request, err := upload.NewRequest(endpoint, extraParams, "package[package_file]", pkgFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
request.SetBasicAuth(c.token, "")
|
|
request.Header.Add("User-Agent", UserAgent)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
return decodeResponse(resp, &struct{}{})
|
|
}
|
|
|
|
type Package struct {
|
|
Name string `json:"name"`
|
|
Filename string `json:"filename"`
|
|
DistroVersion string `json:"distro_version"`
|
|
Version string `json:"version"`
|
|
Release string `json:"release"`
|
|
Type string `json:"type"`
|
|
PackageUrl string `json:"package_url"`
|
|
PackageHtmlUrl string `json:"package_html_url"`
|
|
}
|
|
|
|
// All list all packages in repository
|
|
func (c Client) All(repo string) ([]Package, error) {
|
|
endpoint := fmt.Sprintf("%s/repos/%s/packages.json", ServiceURL, repo)
|
|
return c.paginatedRequest(http.MethodGet, endpoint)
|
|
}
|
|
|
|
// Destroy removes package from repository.
|
|
//
|
|
// repo should be full path to repository
|
|
// (e.g. youruser/repository/ubuntu/xenial).
|
|
func (c Client) Destroy(repo, packageFilename string) error {
|
|
endpoint := fmt.Sprintf("%s/repos/%s/%s", ServiceURL, repo, packageFilename)
|
|
_, err := c.apiRequest(http.MethodDelete, endpoint, &struct{}{})
|
|
|
|
return err
|
|
}
|
|
|
|
// Search searches packages from repository.
|
|
// repo should be full path to repository
|
|
// (e.g. youruser/repository/ubuntu/xenial).
|
|
// q: The query string to search for package filename. If empty string is passed, all packages are returned
|
|
// filter: Search by package type (RPMs, Debs, DSCs, Gem, Python) - Ignored when dist != ""
|
|
// dist: The name of the distribution the package is in. (i.e. ubuntu, el/6) - Overrides filter.
|
|
// perPage: The number of packages to return from the results set. If nothing passed, default is 30
|
|
func (c Client) Search(repo, q, filter, dist string, perPage int) ([]Package, error) {
|
|
endpoint := fmt.Sprintf("%s/repos/%s/search.json?q=%s&filter=%s&dist=%s&per_page=%d", ServiceURL, repo, q, filter, dist, perPage)
|
|
return c.paginatedRequest(http.MethodGet, endpoint)
|
|
}
|
|
|
|
|
|
func (c Client) paginatedRequest(method string, endpoint string) ([]Package, error) {
|
|
var (
|
|
allPackages []Package
|
|
getLastPage bool
|
|
)
|
|
|
|
newPage := make(chan bool)
|
|
total := make(chan int)
|
|
|
|
if c.progressBar {
|
|
getLastPage = true
|
|
progBar := pb.New(0)
|
|
progBar.SetMaxWidth(100)
|
|
progBar.Start()
|
|
defer func() {
|
|
progBar.Increment()
|
|
progBar.Finish()
|
|
}()
|
|
|
|
go func() {
|
|
for {
|
|
select {
|
|
case np := <-newPage:
|
|
if !np {
|
|
return
|
|
}
|
|
progBar.Increment()
|
|
case t := <-total:
|
|
progBar.SetTotal(t)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
for {
|
|
var pkgs []Package
|
|
var group link.Group
|
|
group, err := c.apiRequest(method, endpoint, &pkgs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
allPackages = append(allPackages, pkgs...)
|
|
|
|
next, found := group["next"]
|
|
newPage <- found
|
|
if !found {
|
|
break
|
|
}
|
|
endpoint = next.URI
|
|
|
|
if !getLastPage {
|
|
continue
|
|
}
|
|
|
|
if last, found := group["last"]; found {
|
|
re := regexp.MustCompile(`page=(\d+)$`)
|
|
pages, err := strconv.Atoi(re.FindStringSubmatch(last.URI)[1])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
getLastPage = false
|
|
total <- pages
|
|
}
|
|
}
|
|
|
|
return allPackages, nil
|
|
|
|
}
|
|
|
|
func (c Client) apiRequest(method string, endpoint string, decodedResp interface{}) (link.Group, error) {
|
|
req, err := http.NewRequest(method, endpoint, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.SetBasicAuth(c.token, "")
|
|
req.Header.Add("User-Agent", UserAgent)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
err = decodeResponse(resp, decodedResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// API are paginated, with next page in the response header, as "Link"
|
|
// element
|
|
return link.ParseResponse(resp), nil
|
|
}
|