mirror of
https://github.com/portainer/portainer.git
synced 2025-07-22 14:59:41 +02:00
108 lines
3 KiB
Go
108 lines
3 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/segmentio/encoding/json"
|
|
"oras.land/oras-go/v2/registry/remote/retry"
|
|
)
|
|
|
|
const GitHubAPIHost = "https://api.github.com"
|
|
|
|
// Package represents a GitHub container package
|
|
type Package struct {
|
|
Name string `json:"name"`
|
|
Owner struct {
|
|
Login string `json:"login"`
|
|
} `json:"owner"`
|
|
}
|
|
|
|
// Client represents a GitHub API client
|
|
type Client struct {
|
|
httpClient *http.Client
|
|
baseURL string
|
|
}
|
|
|
|
// NewClient creates a new GitHub API client
|
|
func NewClient(token string) *Client {
|
|
return &Client{
|
|
httpClient: NewHTTPClient(token),
|
|
baseURL: GitHubAPIHost,
|
|
}
|
|
}
|
|
|
|
// GetContainerPackages fetches container packages for the configured namespace
|
|
// It's a small http client wrapper instead of using the github client because listing repositories is the only known operation that isn't directly supported by oras
|
|
func (c *Client) GetContainerPackages(ctx context.Context, useOrganisation bool, organisationName string) ([]string, error) {
|
|
// Determine the namespace (user or organisation) for the request
|
|
namespace := "user"
|
|
if useOrganisation {
|
|
namespace = "orgs/" + organisationName
|
|
}
|
|
|
|
// Build the full URL for listing container packages
|
|
url := fmt.Sprintf("%s/%s/packages?package_type=container", c.baseURL, namespace)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to execute request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, resp.Status)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
|
|
var packages []Package
|
|
if err := json.Unmarshal(body, &packages); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
// Extract repository names in the form "owner/name"
|
|
repositories := make([]string, len(packages))
|
|
for i, pkg := range packages {
|
|
repositories[i] = fmt.Sprintf("%s/%s", strings.ToLower(pkg.Owner.Login), strings.ToLower(pkg.Name))
|
|
}
|
|
|
|
return repositories, nil
|
|
}
|
|
|
|
// NewHTTPClient creates a new HTTP client configured for GitHub API requests
|
|
func NewHTTPClient(token string) *http.Client {
|
|
return &http.Client{
|
|
Transport: &tokenTransport{
|
|
token: token,
|
|
transport: retry.NewTransport(&http.Transport{}), // Use ORAS retry transport for consistent rate limiting and error handling
|
|
},
|
|
Timeout: 1 * time.Minute,
|
|
}
|
|
}
|
|
|
|
// tokenTransport automatically adds the Bearer token header to requests
|
|
type tokenTransport struct {
|
|
token string
|
|
transport http.RoundTripper
|
|
}
|
|
|
|
func (t *tokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
if t.token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+t.token)
|
|
req.Header.Set("Accept", "application/vnd.github+json")
|
|
}
|
|
return t.transport.RoundTrip(req)
|
|
}
|