diff --git a/cmd/osbuild-composer/composer.go b/cmd/osbuild-composer/composer.go index 29e200be3..89477a575 100644 --- a/cmd/osbuild-composer/composer.go +++ b/cmd/osbuild-composer/composer.go @@ -247,7 +247,6 @@ func (c *Composer) Start() error { if c.apiListener != nil { go func() { - const apiRoute = "/api/composer/v1" const apiRouteV2 = "/api/image-builder-composer/v2" const kojiRoute = "/api/composer-koji/v1" @@ -256,7 +255,6 @@ func (c *Composer) Start() error { // Add a "/" here, because http.ServeMux expects the // trailing slash for rooted subtrees, whereas the // handler functions don't. - mux.Handle(apiRoute+"/", c.api.V1(apiRoute)) mux.Handle(apiRouteV2+"/", c.api.V2(apiRouteV2)) mux.Handle(kojiRoute+"/", c.koji.Handler(kojiRoute)) diff --git a/internal/cloudapi/server.go b/internal/cloudapi/server.go index 13ec1043a..9135c2a41 100644 --- a/internal/cloudapi/server.go +++ b/internal/cloudapi/server.go @@ -7,27 +7,20 @@ import ( "github.com/osbuild/osbuild-composer/internal/rpmmd" "github.com/osbuild/osbuild-composer/internal/worker" - v1 "github.com/osbuild/osbuild-composer/internal/cloudapi/v1" v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2" ) type Server struct { - v1 *v1.Server v2 *v2.Server } func NewServer(workers *worker.Server, rpmMetadata rpmmd.RPMMD, distros *distroregistry.Registry, awsBucket string) *Server { server := &Server{ - v1: v1.NewServer(workers, rpmMetadata, distros), v2: v2.NewServer(workers, rpmMetadata, distros, awsBucket), } return server } -func (server *Server) V1(path string) http.Handler { - return server.v1.Handler(path) -} - func (server *Server) V2(path string) http.Handler { return server.v2.Handler(path) } diff --git a/internal/cloudapi/v1/openapi.v1.gen.go b/internal/cloudapi/v1/openapi.v1.gen.go deleted file mode 100644 index f9c1fdf3f..000000000 --- a/internal/cloudapi/v1/openapi.v1.gen.go +++ /dev/null @@ -1,1140 +0,0 @@ -// Package v1 provides primitives to interact the openapi HTTP API. -// -// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT. -package v1 - -import ( - "bytes" - "compress/gzip" - "context" - "encoding/base64" - "encoding/json" - "fmt" - "github.com/deepmap/oapi-codegen/pkg/runtime" - "github.com/getkin/kin-openapi/openapi3" - "github.com/labstack/echo/v4" - "io" - "io/ioutil" - "net/http" - "net/url" - "strings" -) - -// AWSS3UploadRequestOptions defines model for AWSS3UploadRequestOptions. -type AWSS3UploadRequestOptions struct { - Region string `json:"region"` - S3 AWSUploadRequestOptionsS3 `json:"s3"` -} - -// AWSS3UploadStatus defines model for AWSS3UploadStatus. -type AWSS3UploadStatus struct { - Url string `json:"url"` -} - -// AWSUploadRequestOptions defines model for AWSUploadRequestOptions. -type AWSUploadRequestOptions struct { - Ec2 AWSUploadRequestOptionsEc2 `json:"ec2"` - Region string `json:"region"` - S3 AWSUploadRequestOptionsS3 `json:"s3"` -} - -// AWSUploadRequestOptionsEc2 defines model for AWSUploadRequestOptionsEc2. -type AWSUploadRequestOptionsEc2 struct { - AccessKeyId string `json:"access_key_id"` - SecretAccessKey string `json:"secret_access_key"` - SessionToken *string `json:"session_token,omitempty"` - ShareWithAccounts *[]string `json:"share_with_accounts,omitempty"` - SnapshotName *string `json:"snapshot_name,omitempty"` -} - -// AWSUploadRequestOptionsS3 defines model for AWSUploadRequestOptionsS3. -type AWSUploadRequestOptionsS3 struct { - AccessKeyId string `json:"access_key_id"` - Bucket string `json:"bucket"` - SecretAccessKey string `json:"secret_access_key"` - SessionToken *string `json:"session_token,omitempty"` -} - -// AWSUploadStatus defines model for AWSUploadStatus. -type AWSUploadStatus struct { - Ami string `json:"ami"` - Region string `json:"region"` -} - -// AzureUploadRequestOptions defines model for AzureUploadRequestOptions. -type AzureUploadRequestOptions struct { - - // Name of the uploaded image. It must be unique in the given resource group. - // If name is omitted from the request, a random one based on a UUID is - // generated. - ImageName *string `json:"image_name,omitempty"` - - // Location where the image should be uploaded and registered. This link explain - // how to list all locations: - // https://docs.microsoft.com/en-us/cli/azure/account?view=azure-cli-latest#az_account_list_locations' - Location string `json:"location"` - - // Name of the resource group where the image should be uploaded. - ResourceGroup string `json:"resource_group"` - - // ID of subscription where the image should be uploaded. - SubscriptionId string `json:"subscription_id"` - - // ID of the tenant where the image should be uploaded. This link explains how - // to find it in the Azure Portal: - // https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant - TenantId string `json:"tenant_id"` -} - -// AzureUploadStatus defines model for AzureUploadStatus. -type AzureUploadStatus struct { - ImageName string `json:"image_name"` -} - -// ComposeMetadata defines model for ComposeMetadata. -type ComposeMetadata struct { - - // ID (hash) of the built commit - OstreeCommit *string `json:"ostree_commit,omitempty"` - - // Package list including NEVRA - Packages *[]PackageMetadata `json:"packages,omitempty"` -} - -// ComposeRequest defines model for ComposeRequest. -type ComposeRequest struct { - Customizations *Customizations `json:"customizations,omitempty"` - Distribution string `json:"distribution"` - ImageRequests []ImageRequest `json:"image_requests"` -} - -// ComposeResult defines model for ComposeResult. -type ComposeResult struct { - Id string `json:"id"` -} - -// ComposeStatus defines model for ComposeStatus. -type ComposeStatus struct { - ImageStatus ImageStatus `json:"image_status"` -} - -// Customizations defines model for Customizations. -type Customizations struct { - Packages *[]string `json:"packages,omitempty"` - Subscription *Subscription `json:"subscription,omitempty"` - Users *[]User `json:"users,omitempty"` -} - -// GCPUploadRequestOptions defines model for GCPUploadRequestOptions. -type GCPUploadRequestOptions struct { - - // Name of an existing STANDARD Storage class Bucket. - Bucket string `json:"bucket"` - - // The name to use for the imported and shared Compute Engine image. - // The image name must be unique within the GCP project, which is used - // for the OS image upload and import. If not specified a random - // 'composer-api-' string is used as the image name. - ImageName *string `json:"image_name,omitempty"` - - // The GCP region where the OS image will be imported to and shared from. - // The value must be a valid GCP location. See https://cloud.google.com/storage/docs/locations. - // If not specified, the multi-region location closest to the source - // (source Storage Bucket location) is chosen automatically. - Region *string `json:"region,omitempty"` - - // List of valid Google accounts to share the imported Compute Engine image with. - // Each string must contain a specifier of the account type. Valid formats are: - // - 'user:{emailid}': An email address that represents a specific - // Google account. For example, 'alice@example.com'. - // - 'serviceAccount:{emailid}': An email address that represents a - // service account. For example, 'my-other-app@appspot.gserviceaccount.com'. - // - 'group:{emailid}': An email address that represents a Google group. - // For example, 'admins@example.com'. - // - 'domain:{domain}': The G Suite domain (primary) that represents all - // the users of that domain. For example, 'google.com' or 'example.com'. - // If not specified, the imported Compute Engine image is not shared with any - // account. - ShareWithAccounts *[]string `json:"share_with_accounts,omitempty"` -} - -// GCPUploadStatus defines model for GCPUploadStatus. -type GCPUploadStatus struct { - ImageName string `json:"image_name"` - ProjectId string `json:"project_id"` -} - -// ImageRequest defines model for ImageRequest. -type ImageRequest struct { - Architecture string `json:"architecture"` - ImageType string `json:"image_type"` - Ostree *OSTree `json:"ostree,omitempty"` - Repositories []Repository `json:"repositories"` - UploadRequest UploadRequest `json:"upload_request"` -} - -// ImageStatus defines model for ImageStatus. -type ImageStatus struct { - Status ImageStatusValue `json:"status"` - UploadStatus *UploadStatus `json:"upload_status,omitempty"` -} - -// ImageStatusValue defines model for ImageStatusValue. -type ImageStatusValue string - -// List of ImageStatusValue -const ( - ImageStatusValue_building ImageStatusValue = "building" - ImageStatusValue_failure ImageStatusValue = "failure" - ImageStatusValue_pending ImageStatusValue = "pending" - ImageStatusValue_registering ImageStatusValue = "registering" - ImageStatusValue_success ImageStatusValue = "success" - ImageStatusValue_uploading ImageStatusValue = "uploading" -) - -// OSTree defines model for OSTree. -type OSTree struct { - Ref *string `json:"ref,omitempty"` - Url *string `json:"url,omitempty"` -} - -// PackageMetadata defines model for PackageMetadata. -type PackageMetadata struct { - Arch string `json:"arch"` - Epoch *string `json:"epoch,omitempty"` - Name string `json:"name"` - Release string `json:"release"` - Sigmd5 string `json:"sigmd5"` - Signature *string `json:"signature,omitempty"` - Type string `json:"type"` - Version string `json:"version"` -} - -// Repository defines model for Repository. -type Repository struct { - Baseurl *string `json:"baseurl,omitempty"` - Metalink *string `json:"metalink,omitempty"` - Mirrorlist *string `json:"mirrorlist,omitempty"` - Rhsm bool `json:"rhsm"` -} - -// Subscription defines model for Subscription. -type Subscription struct { - ActivationKey string `json:"activation-key"` - BaseUrl string `json:"base-url"` - Insights bool `json:"insights"` - Organization int `json:"organization"` - ServerUrl string `json:"server-url"` -} - -// UploadRequest defines model for UploadRequest. -type UploadRequest struct { - Options interface{} `json:"options"` - Type UploadTypes `json:"type"` -} - -// UploadStatus defines model for UploadStatus. -type UploadStatus struct { - Options interface{} `json:"options"` - Status string `json:"status"` - Type UploadTypes `json:"type"` -} - -// UploadTypes defines model for UploadTypes. -type UploadTypes string - -// List of UploadTypes -const ( - UploadTypes_aws UploadTypes = "aws" - UploadTypes_aws_s3 UploadTypes = "aws.s3" - UploadTypes_azure UploadTypes = "azure" - UploadTypes_gcp UploadTypes = "gcp" -) - -// User defines model for User. -type User struct { - Groups *[]string `json:"groups,omitempty"` - Key *string `json:"key,omitempty"` - Name string `json:"name"` -} - -// Version defines model for Version. -type Version struct { - Version string `json:"version"` -} - -// ComposeJSONBody defines parameters for Compose. -type ComposeJSONBody ComposeRequest - -// ComposeRequestBody defines body for Compose for application/json ContentType. -type ComposeJSONRequestBody ComposeJSONBody - -// RequestEditorFn is the function signature for the RequestEditor callback function -type RequestEditorFn func(ctx context.Context, req *http.Request) error - -// Doer performs HTTP requests. -// -// The standard http.Client implements this interface. -type HttpRequestDoer interface { - Do(req *http.Request) (*http.Response, error) -} - -// Client which conforms to the OpenAPI3 specification for this service. -type Client struct { - // The endpoint of the server conforming to this interface, with scheme, - // https://api.deepmap.com for example. - Server string - - // Doer for performing requests, typically a *http.Client with any - // customized settings, such as certificate chains. - Client HttpRequestDoer - - // A callback for modifying requests which are generated before sending over - // the network. - RequestEditor RequestEditorFn -} - -// ClientOption allows setting custom parameters during construction -type ClientOption func(*Client) error - -// Creates a new Client, with reasonable defaults -func NewClient(server string, opts ...ClientOption) (*Client, error) { - // create a client with sane default values - client := Client{ - Server: server, - } - // mutate client and add all optional params - for _, o := range opts { - if err := o(&client); err != nil { - return nil, err - } - } - // ensure the server URL always has a trailing slash - if !strings.HasSuffix(client.Server, "/") { - client.Server += "/" - } - // create httpClient, if not already present - if client.Client == nil { - client.Client = http.DefaultClient - } - return &client, nil -} - -// WithHTTPClient allows overriding the default Doer, which is -// automatically created using http.Client. This is useful for tests. -func WithHTTPClient(doer HttpRequestDoer) ClientOption { - return func(c *Client) error { - c.Client = doer - return nil - } -} - -// WithRequestEditorFn allows setting up a callback function, which will be -// called right before sending the request. This can be used to mutate the request. -func WithRequestEditorFn(fn RequestEditorFn) ClientOption { - return func(c *Client) error { - c.RequestEditor = fn - return nil - } -} - -// The interface specification for the client above. -type ClientInterface interface { - // Compose request with any body - ComposeWithBody(ctx context.Context, contentType string, body io.Reader) (*http.Response, error) - - Compose(ctx context.Context, body ComposeJSONRequestBody) (*http.Response, error) - - // ComposeStatus request - ComposeStatus(ctx context.Context, id string) (*http.Response, error) - - // ComposeMetadata request - ComposeMetadata(ctx context.Context, id string) (*http.Response, error) - - // GetOpenapiJson request - GetOpenapiJson(ctx context.Context) (*http.Response, error) - - // GetVersion request - GetVersion(ctx context.Context) (*http.Response, error) -} - -func (c *Client) ComposeWithBody(ctx context.Context, contentType string, body io.Reader) (*http.Response, error) { - req, err := NewComposeRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if c.RequestEditor != nil { - err = c.RequestEditor(ctx, req) - if err != nil { - return nil, err - } - } - return c.Client.Do(req) -} - -func (c *Client) Compose(ctx context.Context, body ComposeJSONRequestBody) (*http.Response, error) { - req, err := NewComposeRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if c.RequestEditor != nil { - err = c.RequestEditor(ctx, req) - if err != nil { - return nil, err - } - } - return c.Client.Do(req) -} - -func (c *Client) ComposeStatus(ctx context.Context, id string) (*http.Response, error) { - req, err := NewComposeStatusRequest(c.Server, id) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if c.RequestEditor != nil { - err = c.RequestEditor(ctx, req) - if err != nil { - return nil, err - } - } - return c.Client.Do(req) -} - -func (c *Client) ComposeMetadata(ctx context.Context, id string) (*http.Response, error) { - req, err := NewComposeMetadataRequest(c.Server, id) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if c.RequestEditor != nil { - err = c.RequestEditor(ctx, req) - if err != nil { - return nil, err - } - } - return c.Client.Do(req) -} - -func (c *Client) GetOpenapiJson(ctx context.Context) (*http.Response, error) { - req, err := NewGetOpenapiJsonRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if c.RequestEditor != nil { - err = c.RequestEditor(ctx, req) - if err != nil { - return nil, err - } - } - return c.Client.Do(req) -} - -func (c *Client) GetVersion(ctx context.Context) (*http.Response, error) { - req, err := NewGetVersionRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if c.RequestEditor != nil { - err = c.RequestEditor(ctx, req) - if err != nil { - return nil, err - } - } - return c.Client.Do(req) -} - -// NewComposeRequest calls the generic Compose builder with application/json body -func NewComposeRequest(server string, body ComposeJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewComposeRequestWithBody(server, "application/json", bodyReader) -} - -// NewComposeRequestWithBody generates requests for Compose with any type of body -func NewComposeRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - queryUrl, err := url.Parse(server) - if err != nil { - return nil, err - } - - basePath := fmt.Sprintf("/compose") - if basePath[0] == '/' { - basePath = basePath[1:] - } - - queryUrl, err = queryUrl.Parse(basePath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryUrl.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - return req, nil -} - -// NewComposeStatusRequest generates requests for ComposeStatus -func NewComposeStatusRequest(server string, id string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParam("simple", false, "id", id) - if err != nil { - return nil, err - } - - queryUrl, err := url.Parse(server) - if err != nil { - return nil, err - } - - basePath := fmt.Sprintf("/compose/%s", pathParam0) - if basePath[0] == '/' { - basePath = basePath[1:] - } - - queryUrl, err = queryUrl.Parse(basePath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryUrl.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewComposeMetadataRequest generates requests for ComposeMetadata -func NewComposeMetadataRequest(server string, id string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParam("simple", false, "id", id) - if err != nil { - return nil, err - } - - queryUrl, err := url.Parse(server) - if err != nil { - return nil, err - } - - basePath := fmt.Sprintf("/compose/%s/metadata", pathParam0) - if basePath[0] == '/' { - basePath = basePath[1:] - } - - queryUrl, err = queryUrl.Parse(basePath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryUrl.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetOpenapiJsonRequest generates requests for GetOpenapiJson -func NewGetOpenapiJsonRequest(server string) (*http.Request, error) { - var err error - - queryUrl, err := url.Parse(server) - if err != nil { - return nil, err - } - - basePath := fmt.Sprintf("/openapi.json") - if basePath[0] == '/' { - basePath = basePath[1:] - } - - queryUrl, err = queryUrl.Parse(basePath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryUrl.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetVersionRequest generates requests for GetVersion -func NewGetVersionRequest(server string) (*http.Request, error) { - var err error - - queryUrl, err := url.Parse(server) - if err != nil { - return nil, err - } - - basePath := fmt.Sprintf("/version") - if basePath[0] == '/' { - basePath = basePath[1:] - } - - queryUrl, err = queryUrl.Parse(basePath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryUrl.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} - -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) - if err != nil { - return nil, err - } - return &ClientWithResponses{client}, nil -} - -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil - } -} - -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { - // Compose request with any body - ComposeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader) (*ComposeResponse, error) - - ComposeWithResponse(ctx context.Context, body ComposeJSONRequestBody) (*ComposeResponse, error) - - // ComposeStatus request - ComposeStatusWithResponse(ctx context.Context, id string) (*ComposeStatusResponse, error) - - // ComposeMetadata request - ComposeMetadataWithResponse(ctx context.Context, id string) (*ComposeMetadataResponse, error) - - // GetOpenapiJson request - GetOpenapiJsonWithResponse(ctx context.Context) (*GetOpenapiJsonResponse, error) - - // GetVersion request - GetVersionWithResponse(ctx context.Context) (*GetVersionResponse, error) -} - -type ComposeResponse struct { - Body []byte - HTTPResponse *http.Response - JSON201 *ComposeResult -} - -// Status returns HTTPResponse.Status -func (r ComposeResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ComposeResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type ComposeStatusResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ComposeStatus -} - -// Status returns HTTPResponse.Status -func (r ComposeStatusResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ComposeStatusResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type ComposeMetadataResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ComposeMetadata -} - -// Status returns HTTPResponse.Status -func (r ComposeMetadataResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ComposeMetadataResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetOpenapiJsonResponse struct { - Body []byte - HTTPResponse *http.Response -} - -// Status returns HTTPResponse.Status -func (r GetOpenapiJsonResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetOpenapiJsonResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetVersionResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *Version -} - -// Status returns HTTPResponse.Status -func (r GetVersionResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetVersionResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ComposeWithBodyWithResponse request with arbitrary body returning *ComposeResponse -func (c *ClientWithResponses) ComposeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader) (*ComposeResponse, error) { - rsp, err := c.ComposeWithBody(ctx, contentType, body) - if err != nil { - return nil, err - } - return ParseComposeResponse(rsp) -} - -func (c *ClientWithResponses) ComposeWithResponse(ctx context.Context, body ComposeJSONRequestBody) (*ComposeResponse, error) { - rsp, err := c.Compose(ctx, body) - if err != nil { - return nil, err - } - return ParseComposeResponse(rsp) -} - -// ComposeStatusWithResponse request returning *ComposeStatusResponse -func (c *ClientWithResponses) ComposeStatusWithResponse(ctx context.Context, id string) (*ComposeStatusResponse, error) { - rsp, err := c.ComposeStatus(ctx, id) - if err != nil { - return nil, err - } - return ParseComposeStatusResponse(rsp) -} - -// ComposeMetadataWithResponse request returning *ComposeMetadataResponse -func (c *ClientWithResponses) ComposeMetadataWithResponse(ctx context.Context, id string) (*ComposeMetadataResponse, error) { - rsp, err := c.ComposeMetadata(ctx, id) - if err != nil { - return nil, err - } - return ParseComposeMetadataResponse(rsp) -} - -// GetOpenapiJsonWithResponse request returning *GetOpenapiJsonResponse -func (c *ClientWithResponses) GetOpenapiJsonWithResponse(ctx context.Context) (*GetOpenapiJsonResponse, error) { - rsp, err := c.GetOpenapiJson(ctx) - if err != nil { - return nil, err - } - return ParseGetOpenapiJsonResponse(rsp) -} - -// GetVersionWithResponse request returning *GetVersionResponse -func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context) (*GetVersionResponse, error) { - rsp, err := c.GetVersion(ctx) - if err != nil { - return nil, err - } - return ParseGetVersionResponse(rsp) -} - -// ParseComposeResponse parses an HTTP response from a ComposeWithResponse call -func ParseComposeResponse(rsp *http.Response) (*ComposeResponse, error) { - bodyBytes, err := ioutil.ReadAll(rsp.Body) - defer rsp.Body.Close() - if err != nil { - return nil, err - } - - response := &ComposeResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest ComposeResult - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - - } - - return response, nil -} - -// ParseComposeStatusResponse parses an HTTP response from a ComposeStatusWithResponse call -func ParseComposeStatusResponse(rsp *http.Response) (*ComposeStatusResponse, error) { - bodyBytes, err := ioutil.ReadAll(rsp.Body) - defer rsp.Body.Close() - if err != nil { - return nil, err - } - - response := &ComposeStatusResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ComposeStatus - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -} - -// ParseComposeMetadataResponse parses an HTTP response from a ComposeMetadataWithResponse call -func ParseComposeMetadataResponse(rsp *http.Response) (*ComposeMetadataResponse, error) { - bodyBytes, err := ioutil.ReadAll(rsp.Body) - defer rsp.Body.Close() - if err != nil { - return nil, err - } - - response := &ComposeMetadataResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ComposeMetadata - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -} - -// ParseGetOpenapiJsonResponse parses an HTTP response from a GetOpenapiJsonWithResponse call -func ParseGetOpenapiJsonResponse(rsp *http.Response) (*GetOpenapiJsonResponse, error) { - bodyBytes, err := ioutil.ReadAll(rsp.Body) - defer rsp.Body.Close() - if err != nil { - return nil, err - } - - response := &GetOpenapiJsonResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - } - - return response, nil -} - -// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call -func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { - bodyBytes, err := ioutil.ReadAll(rsp.Body) - defer rsp.Body.Close() - if err != nil { - return nil, err - } - - response := &GetVersionResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Version - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -} - -// ServerInterface represents all server handlers. -type ServerInterface interface { - // Create compose - // (POST /compose) - Compose(ctx echo.Context) error - // The status of a compose - // (GET /compose/{id}) - ComposeStatus(ctx echo.Context, id string) error - // Get the metadata for a compose. - // (GET /compose/{id}/metadata) - ComposeMetadata(ctx echo.Context, id string) error - // get the openapi json specification - // (GET /openapi.json) - GetOpenapiJson(ctx echo.Context) error - // get the service version - // (GET /version) - GetVersion(ctx echo.Context) error -} - -// ServerInterfaceWrapper converts echo contexts to parameters. -type ServerInterfaceWrapper struct { - Handler ServerInterface -} - -// Compose converts echo context to params. -func (w *ServerInterfaceWrapper) Compose(ctx echo.Context) error { - var err error - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.Compose(ctx) - return err -} - -// ComposeStatus converts echo context to params. -func (w *ServerInterfaceWrapper) ComposeStatus(ctx echo.Context) error { - var err error - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameter("simple", false, "id", ctx.Param("id"), &id) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) - } - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.ComposeStatus(ctx, id) - return err -} - -// ComposeMetadata converts echo context to params. -func (w *ServerInterfaceWrapper) ComposeMetadata(ctx echo.Context) error { - var err error - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameter("simple", false, "id", ctx.Param("id"), &id) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) - } - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.ComposeMetadata(ctx, id) - return err -} - -// GetOpenapiJson converts echo context to params. -func (w *ServerInterfaceWrapper) GetOpenapiJson(ctx echo.Context) error { - var err error - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.GetOpenapiJson(ctx) - return err -} - -// GetVersion converts echo context to params. -func (w *ServerInterfaceWrapper) GetVersion(ctx echo.Context) error { - var err error - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.GetVersion(ctx) - return err -} - -// This is a simple interface which specifies echo.Route addition functions which -// are present on both echo.Echo and echo.Group, since we want to allow using -// either of them for path registration -type EchoRouter interface { - CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route - TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route -} - -// RegisterHandlers adds each server route to the EchoRouter. -func RegisterHandlers(router EchoRouter, si ServerInterface) { - - wrapper := ServerInterfaceWrapper{ - Handler: si, - } - - router.POST("/compose", wrapper.Compose) - router.GET("/compose/:id", wrapper.ComposeStatus) - router.GET("/compose/:id/metadata", wrapper.ComposeMetadata) - router.GET("/openapi.json", wrapper.GetOpenapiJson) - router.GET("/version", wrapper.GetVersion) - -} - -// Base64 encoded, gzipped, json marshaled Swagger object -var swaggerSpec = []string{ - - "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", - "LsQVEesNVN319PTfAAAA//+aWSukOTEAAA==", -} - -// GetSwagger returns the Swagger specification corresponding to the generated code -// in this file. -func GetSwagger() (*openapi3.Swagger, error) { - zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) - if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) - } - zr, err := gzip.NewReader(bytes.NewReader(zipped)) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) - } - var buf bytes.Buffer - _, err = buf.ReadFrom(zr) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) - } - - swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(buf.Bytes()) - if err != nil { - return nil, fmt.Errorf("error loading Swagger: %s", err) - } - return swagger, nil -} diff --git a/internal/cloudapi/v1/openapi.v1.yml b/internal/cloudapi/v1/openapi.v1.yml deleted file mode 100644 index 5c5b52a1f..000000000 --- a/internal/cloudapi/v1/openapi.v1.yml +++ /dev/null @@ -1,539 +0,0 @@ ---- -openapi: 3.0.1 -info: - version: '1' - title: OSBuild Composer cloud api - description: Service to build and install images. - license: - name: Apache 2.0 - url: https://www.apache.org/licenses/LICENSE-2.0.html - -paths: - /version: - get: - summary: get the service version - description: "get the service version" - operationId: getVersion - responses: - '200': - description: a service version - content: - application/json: - schema: - $ref: '#/components/schemas/Version' - /openapi.json: - get: - summary: get the openapi json specification - operationId: getOpenapiJson - tags: - - meta - responses: - '200': - description: returns this document - /compose/{id}: - get: - summary: The status of a compose - parameters: - - in: path - name: id - schema: - type: string - format: uuid - example: '123e4567-e89b-12d3-a456-426655440000' - required: true - description: ID of compose status to get - description: Get the status of a running or completed compose. This includes whether or not it succeeded, and also meta information about the result. - operationId: compose_status - responses: - '200': - description: compose status - content: - application/json: - schema: - $ref: '#/components/schemas/ComposeStatus' - '400': - description: Invalid compose id - content: - text/plain: - schema: - type: string - '404': - description: Unknown compose id - content: - text/plain: - schema: - type: string - /compose/{id}/metadata: - get: - summary: Get the metadata for a compose. - operationId: compose_metadata - parameters: - - in: path - name: id - schema: - type: string - format: uuid - example: 123e4567-e89b-12d3-a456-426655440000 - required: true - description: ID of compose status to get - description: 'Get the metadata of a finished compose. The exact information returned depends on the requested image type.' - responses: - '200': - description: The metadata for the given compose. - content: - application/json: - schema: - $ref: '#/components/schemas/ComposeMetadata' - '400': - description: Invalid compose id - content: - text/plain: - schema: - type: string - '404': - description: Unknown compose id - content: - text/plain: - schema: - type: string - /compose: - post: - summary: Create compose - description: Create a new compose, potentially consisting of several images and upload each to their destinations. - operationId: compose - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ComposeRequest' - responses: - '201': - description: Compose has started - content: - application/json: - schema: - $ref: '#/components/schemas/ComposeResult' - -components: - schemas: - Version: - required: - - version - properties: - version: - type: string - ComposeStatus: - required: - - image_status - properties: - image_status: - $ref: '#/components/schemas/ImageStatus' - ImageStatus: - required: - - status - properties: - status: - $ref: '#/components/schemas/ImageStatusValue' - upload_status: - $ref: '#/components/schemas/UploadStatus' - ImageStatusValue: - type: string - enum: ['success', 'failure', 'pending', 'building', 'uploading', 'registering'] - UploadStatus: - required: - - status - - type - - options - properties: - status: - type: string - enum: ['success', 'failure', 'pending', 'running'] - type: - $ref: '#/components/schemas/UploadTypes' - options: - oneOf: - - $ref: '#/components/schemas/AWSUploadStatus' - - $ref: '#/components/schemas/AWSS3UploadStatus' - - $ref: '#/components/schemas/GCPUploadStatus' - - $ref: '#/components/schemas/AzureUploadStatus' - AWSUploadStatus: - type: object - required: - - ami - - region - properties: - ami: - type: string - example: 'ami-0c830793775595d4b' - region: - type: string - example: 'eu-west-1' - AWSS3UploadStatus: - type: object - required: - - url - properties: - url: - type: string - GCPUploadStatus: - type: object - required: - - project_id - - image_name - properties: - project_id: - type: string - example: 'ascendant-braid-303513' - image_name: - type: string - example: 'my-image' - AzureUploadStatus: - type: object - required: - - image_name - properties: - image_name: - type: string - example: 'my-image' - ComposeMetadata: - type: object - properties: - packages: - type: array - items: - $ref: '#/components/schemas/PackageMetadata' - description: 'Package list including NEVRA' - ostree_commit: - type: string - description: 'ID (hash) of the built commit' - ComposeRequest: - type: object - required: - - distribution - - image_requests - properties: - distribution: - type: string - example: 'rhel-8' - image_requests: - type: array - items: - $ref: '#/components/schemas/ImageRequest' - customizations: - $ref: '#/components/schemas/Customizations' - ImageRequest: - required: - - architecture - - image_type - - repositories - - upload_request - properties: - architecture: - type: string - example: 'x86_64' - image_type: - type: string - example: 'ami' - repositories: - type: array - items: - $ref: '#/components/schemas/Repository' - ostree: - $ref: '#/components/schemas/OSTree' - upload_request: - $ref: '#/components/schemas/UploadRequest' - Repository: - type: object - required: - - rhsm - properties: - rhsm: - type: boolean - baseurl: - type: string - format: url - example: 'https://cdn.redhat.com/content/dist/rhel8/8/x86_64/baseos/os/' - mirrorlist: - type: string - format: url - example: 'https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-33&arch=x86_64' - metalink: - type: string - format: url - example: 'https://mirrors.fedoraproject.org/metalink?repo=fedora-32&arch=x86_64' - UploadRequest: - type: object - required: - - type - - options - properties: - type: - $ref: '#/components/schemas/UploadTypes' - options: - oneOf: - - $ref: '#/components/schemas/AWSUploadRequestOptions' - - $ref: '#/components/schemas/AWSS3UploadRequestOptions' - - $ref: '#/components/schemas/GCPUploadRequestOptions' - - $ref: '#/components/schemas/AzureUploadRequestOptions' - UploadTypes: - type: string - enum: ['aws', 'aws.s3', 'gcp', 'azure'] - AWSUploadRequestOptions: - type: object - required: - - region - - s3 - - ec2 - properties: - region: - type: string - example: 'eu-west-1' - s3: - $ref: '#/components/schemas/AWSUploadRequestOptionsS3' - ec2: - $ref: '#/components/schemas/AWSUploadRequestOptionsEc2' - AWSS3UploadRequestOptions: - type: object - required: - - region - - s3 - properties: - region: - type: string - example: 'eu-west-1' - s3: - $ref: '#/components/schemas/AWSUploadRequestOptionsS3' - AWSUploadRequestOptionsS3: - type: object - required: - - access_key_id - - secret_access_key - - bucket - properties: - access_key_id: - type: string - example: 'AKIAIOSFODNN7EXAMPLE' - secret_access_key: - type: string - format: password - example: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' - session_token: - type: string - example: 'AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE' - bucket: - type: string - example: 'my-bucket' - AWSUploadRequestOptionsEc2: - type: object - required: - - access_key_id - - secret_access_key - properties: - access_key_id: - type: string - example: 'AKIAIOSFODNN7EXAMPLE' - secret_access_key: - type: string - format: password - example: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' - session_token: - type: string - example: 'AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE' - snapshot_name: - type: string - example: 'my-snapshot' - share_with_accounts: - type: array - example: ['123456789012'] - items: - type: string - GCPUploadRequestOptions: - type: object - required: - - bucket - properties: - region: - type: string - example: 'eu' - description: | - The GCP region where the OS image will be imported to and shared from. - The value must be a valid GCP location. See https://cloud.google.com/storage/docs/locations. - If not specified, the multi-region location closest to the source - (source Storage Bucket location) is chosen automatically. - bucket: - type: string - example: 'my-example-bucket' - description: 'Name of an existing STANDARD Storage class Bucket.' -# don't expose the os type for now -# os: -# type: string -# example: 'rhel-8-byol' -# description: 'OS of the disk image being imported needed for installation of GCP guest tools.' - image_name: - type: string - example: 'my-image' - description: | - The name to use for the imported and shared Compute Engine image. - The image name must be unique within the GCP project, which is used - for the OS image upload and import. If not specified a random - 'composer-api-' string is used as the image name. - share_with_accounts: - type: array - example: [ - 'user:alice@example.com', - 'serviceAccount:my-other-app@appspot.gserviceaccount.com', - 'group:admins@example.com', - 'domain:example.com' - ] - description: | - List of valid Google accounts to share the imported Compute Engine image with. - Each string must contain a specifier of the account type. Valid formats are: - - 'user:{emailid}': An email address that represents a specific - Google account. For example, 'alice@example.com'. - - 'serviceAccount:{emailid}': An email address that represents a - service account. For example, 'my-other-app@appspot.gserviceaccount.com'. - - 'group:{emailid}': An email address that represents a Google group. - For example, 'admins@example.com'. - - 'domain:{domain}': The G Suite domain (primary) that represents all - the users of that domain. For example, 'google.com' or 'example.com'. - If not specified, the imported Compute Engine image is not shared with any - account. - items: - type: string - AzureUploadRequestOptions: - type: object - required: - - tenant_id - - subscription_id - - resource_group - - location - properties: - tenant_id: - type: string - example: '5c7ef5b6-1c3f-4da0-a622-0b060239d7d7' - description: | - ID of the tenant where the image should be uploaded. This link explains how - to find it in the Azure Portal: - https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant - subscription_id: - type: string - example: '4e5d8b2c-ab24-4413-90c5-612306e809e2' - description: | - ID of subscription where the image should be uploaded. - resource_group: - type: string - example: 'ToucanResourceGroup' - description: | - Name of the resource group where the image should be uploaded. - location: - type: string - example: 'westeurope' - description: | - Location where the image should be uploaded and registered. This link explain - how to list all locations: - https://docs.microsoft.com/en-us/cli/azure/account?view=azure-cli-latest#az_account_list_locations' - image_name: - type: string - example: 'my-image' - description: | - Name of the uploaded image. It must be unique in the given resource group. - If name is omitted from the request, a random one based on a UUID is - generated. - Customizations: - type: object - properties: - subscription: - $ref: '#/components/schemas/Subscription' - packages: - type: array - example: ['postgres'] - items: - type: string - users: - type: array - items: - $ref: '#/components/schemas/User' - OSTree: - type: object - properties: - url: - type: string - ref: - type: string - example: ['rhel/8/x86_64/edge'] - Subscription: - type: object - required: - - organization - - activation-key - - server-url - - base-url - - insights - properties: - organization: - type: integer - example: 2040324 - activation-key: - type: string - format: password - example: 'my-secret-key' - server-url: - type: string - example: 'subscription.rhsm.redhat.com' - base-url: - type: string - format: url - example: http://cdn.redhat.com/ - insights: - type: boolean - example: true - ComposeResult: - required: - - id - properties: - id: - type: string - format: uuid - example: '123e4567-e89b-12d3-a456-426655440000' - PackageMetadata: - required: - - type - - name - - version - - release - - arch - - sigmd5 - properties: - type: - type: string - name: - type: string - version: - type: string - release: - type: string - epoch: - type: string - arch: - type: string - sigmd5: - type: string - signature: - type: string - User: - type: object - required: - - name - properties: - name: - type: string - example: "user1" - groups: - type: array - items: - type: string - example: "group1" - key: - type: string - example: "public ssh key" diff --git a/internal/cloudapi/v1/v1.go b/internal/cloudapi/v1/v1.go deleted file mode 100644 index e1bfc56dc..000000000 --- a/internal/cloudapi/v1/v1.go +++ /dev/null @@ -1,588 +0,0 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=v1 --generate types,spec,client,server -o openapi.v1.gen.go openapi.v1.yml - -package v1 - -import ( - "crypto/rand" - "encoding/json" - "fmt" - "math" - "math/big" - "net/http" - "time" - - "github.com/google/uuid" - "github.com/labstack/echo/v4" - - "github.com/osbuild/osbuild-composer/internal/blueprint" - "github.com/osbuild/osbuild-composer/internal/distro" - "github.com/osbuild/osbuild-composer/internal/distroregistry" - osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2" - "github.com/osbuild/osbuild-composer/internal/ostree" - "github.com/osbuild/osbuild-composer/internal/prometheus" - "github.com/osbuild/osbuild-composer/internal/rpmmd" - "github.com/osbuild/osbuild-composer/internal/target" - "github.com/osbuild/osbuild-composer/internal/worker" -) - -// Server represents the state of the cloud Server -type Server struct { - workers *worker.Server - rpmMetadata rpmmd.RPMMD - distros *distroregistry.Registry -} - -type apiHandlers struct { - server *Server -} - -type binder struct{} - -// NewServer creates a new cloud server -func NewServer(workers *worker.Server, rpmMetadata rpmmd.RPMMD, distros *distroregistry.Registry) *Server { - server := &Server{ - workers: workers, - rpmMetadata: rpmMetadata, - distros: distros, - } - return server -} - -// Create an http.Handler() for this server, that provides the composer API at -// the given path. -func (server *Server) Handler(path string) http.Handler { - e := echo.New() - e.Binder = binder{} - - handler := apiHandlers{ - server: server, - } - RegisterHandlers(e.Group(path, prometheus.MetricsMiddleware), &handler) - return e -} - -func (b binder) Bind(i interface{}, ctx echo.Context) error { - contentType := ctx.Request().Header["Content-Type"] - if len(contentType) != 1 || contentType[0] != "application/json" { - return echo.NewHTTPError(http.StatusUnsupportedMediaType, "Only 'application/json' content type is supported") - } - - err := json.NewDecoder(ctx.Request().Body).Decode(i) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Cannot parse request body: %v", err)) - } - return nil -} - -// Compose handles a new /compose POST request -func (h *apiHandlers) Compose(ctx echo.Context) error { - contentType := ctx.Request().Header["Content-Type"] - if len(contentType) != 1 || contentType[0] != "application/json" { - return echo.NewHTTPError(http.StatusUnsupportedMediaType, "Only 'application/json' content type is supported") - } - - var request ComposeRequest - err := ctx.Bind(&request) - if err != nil { - return err - } - - distribution := h.server.distros.GetDistro(request.Distribution) - if distribution == nil { - return echo.NewHTTPError(http.StatusBadRequest, "Unsupported distribution: %s", request.Distribution) - } - - var bp = blueprint.Blueprint{} - err = bp.Initialize() - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to initialize blueprint") - } - if request.Customizations != nil && request.Customizations.Packages != nil { - for _, p := range *request.Customizations.Packages { - bp.Packages = append(bp.Packages, blueprint.Package{ - Name: p, - }) - } - } - - // imagerequest - type imageRequest struct { - manifest distro.Manifest - arch string - exports []string - pipelineNames worker.PipelineNames - } - imageRequests := make([]imageRequest, len(request.ImageRequests)) - var targets []*target.Target - - // use the same seed for all images so we get the same IDs - bigSeed, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - if err != nil { - panic("cannot generate a manifest seed: " + err.Error()) - } - manifestSeed := bigSeed.Int64() - - for i, ir := range request.ImageRequests { - arch, err := distribution.GetArch(ir.Architecture) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Unsupported architecture '%s' for distribution '%s'", ir.Architecture, request.Distribution) - } - imageType, err := arch.GetImageType(ir.ImageType) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Unsupported image type '%s' for %s/%s", ir.ImageType, ir.Architecture, request.Distribution) - } - repositories := make([]rpmmd.RepoConfig, len(ir.Repositories)) - for j, repo := range ir.Repositories { - repositories[j].RHSM = repo.Rhsm - - if repo.Baseurl != nil { - repositories[j].BaseURL = *repo.Baseurl - } else if repo.Mirrorlist != nil { - repositories[j].MirrorList = *repo.Mirrorlist - } else if repo.Metalink != nil { - repositories[j].Metalink = *repo.Metalink - } else { - return echo.NewHTTPError(http.StatusBadRequest, "Must specify baseurl, mirrorlist, or metalink") - } - } - - packageSets := imageType.PackageSets(bp) - depsolveJobID, err := h.server.workers.EnqueueDepsolve(&worker.DepsolveJob{ - PackageSets: packageSets, - Repos: repositories, - ModulePlatformID: distribution.ModulePlatformID(), - Arch: arch.Name(), - Releasever: distribution.Releasever(), - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to enqueue depsolve job") - } - - var depsolveResults worker.DepsolveJobResult - for { - status, _, err := h.server.workers.JobStatus(depsolveJobID, &depsolveResults) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to get depsolve results") - } - if status.Canceled { - return echo.NewHTTPError(http.StatusInternalServerError, "Depsolving job canceled unexpectedly") - } - if !status.Finished.IsZero() { - break - } - time.Sleep(50 * time.Millisecond) - } - - if depsolveResults.Error != "" { - if depsolveResults.ErrorType == worker.DepsolveErrorType { - return echo.NewHTTPError(http.StatusBadRequest, "Failed to depsolve requested package set: %s", depsolveResults.Error) - } - return echo.NewHTTPError(http.StatusInternalServerError, "Error while depsolving: %s", depsolveResults.Error) - } - pkgSpecSets := depsolveResults.PackageSpecs - - imageOptions := distro.ImageOptions{Size: imageType.Size(0)} - if request.Customizations != nil && request.Customizations.Subscription != nil { - imageOptions.Subscription = &distro.SubscriptionImageOptions{ - Organization: fmt.Sprintf("%d", request.Customizations.Subscription.Organization), - ActivationKey: request.Customizations.Subscription.ActivationKey, - ServerUrl: request.Customizations.Subscription.ServerUrl, - BaseUrl: request.Customizations.Subscription.BaseUrl, - Insights: request.Customizations.Subscription.Insights, - } - } - - // set default ostree ref, if one not provided - ostreeOptions := ir.Ostree - if ostreeOptions == nil || ostreeOptions.Ref == nil { - imageOptions.OSTree = distro.OSTreeImageOptions{Ref: imageType.OSTreeRef()} - } else if !ostree.VerifyRef(*ostreeOptions.Ref) { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid OSTree ref: %s", *ostreeOptions.Ref) - } else { - imageOptions.OSTree = distro.OSTreeImageOptions{Ref: *ostreeOptions.Ref} - } - - var parent string - if ostreeOptions != nil && ostreeOptions.Url != nil { - imageOptions.OSTree.URL = *ostreeOptions.Url - parent, err = ostree.ResolveRef(imageOptions.OSTree.URL, imageOptions.OSTree.Ref) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Error resolving OSTree repo %s: %s", imageOptions.OSTree.URL, err) - } - imageOptions.OSTree.Parent = parent - } - - // Set the blueprint customisation to take care of the user - var blueprintCustoms *blueprint.Customizations - if request.Customizations != nil && request.Customizations.Users != nil { - var userCustomizations []blueprint.UserCustomization - for _, user := range *request.Customizations.Users { - var groups []string - if user.Groups != nil { - groups = *user.Groups - } else { - groups = nil - } - userCustomizations = append(userCustomizations, - blueprint.UserCustomization{ - Name: user.Name, - Key: user.Key, - Groups: groups, - }, - ) - } - blueprintCustoms = &blueprint.Customizations{ - User: userCustomizations, - } - } - - manifest, err := imageType.Manifest(blueprintCustoms, imageOptions, repositories, pkgSpecSets, manifestSeed) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Failed to get manifest for for %s/%s/%s: %s", ir.ImageType, ir.Architecture, request.Distribution, err) - } - - imageRequests[i].manifest = manifest - imageRequests[i].arch = arch.Name() - imageRequests[i].exports = imageType.Exports() - imageRequests[i].pipelineNames = worker.PipelineNames{ - Build: imageType.BuildPipelines(), - Payload: imageType.PayloadPipelines(), - } - - uploadRequest := ir.UploadRequest - /* oneOf is not supported by the openapi generator so marshal and unmarshal the uploadrequest based on the type */ - if uploadRequest.Type == UploadTypes_aws { - var awsUploadOptions AWSUploadRequestOptions - jsonUploadOptions, err := json.Marshal(uploadRequest.Options) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to marshal aws upload request") - } - err = json.Unmarshal(jsonUploadOptions, &awsUploadOptions) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to unmarshal aws upload request") - } - - var share []string - if awsUploadOptions.Ec2.ShareWithAccounts != nil { - share = *awsUploadOptions.Ec2.ShareWithAccounts - } - key := fmt.Sprintf("composer-api-%s", uuid.New().String()) - t := target.NewAWSTarget(&target.AWSTargetOptions{ - Filename: imageType.Filename(), - Region: awsUploadOptions.Region, - AccessKeyID: awsUploadOptions.S3.AccessKeyId, - SecretAccessKey: awsUploadOptions.S3.SecretAccessKey, - Bucket: awsUploadOptions.S3.Bucket, - Key: key, - ShareWithAccounts: share, - }) - if awsUploadOptions.Ec2.SnapshotName != nil { - t.ImageName = *awsUploadOptions.Ec2.SnapshotName - } else { - t.ImageName = key - } - - targets = append(targets, t) - } else if uploadRequest.Type == UploadTypes_aws_s3 { - var awsS3UploadOptions AWSS3UploadRequestOptions - jsonUploadOptions, err := json.Marshal(uploadRequest.Options) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to unmarshal aws upload request") - } - err = json.Unmarshal(jsonUploadOptions, &awsS3UploadOptions) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to unmarshal aws upload request") - } - - key := fmt.Sprintf("composer-api-%s", uuid.New().String()) - t := target.NewAWSS3Target(&target.AWSS3TargetOptions{ - Filename: imageType.Filename(), - Region: awsS3UploadOptions.Region, - AccessKeyID: awsS3UploadOptions.S3.AccessKeyId, - SecretAccessKey: awsS3UploadOptions.S3.SecretAccessKey, - Bucket: awsS3UploadOptions.S3.Bucket, - Key: key, - }) - t.ImageName = key - - targets = append(targets, t) - } else if uploadRequest.Type == UploadTypes_gcp { - var gcpUploadOptions GCPUploadRequestOptions - jsonUploadOptions, err := json.Marshal(uploadRequest.Options) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to marshal gcp upload request") - } - err = json.Unmarshal(jsonUploadOptions, &gcpUploadOptions) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to unmarshal gcp upload request") - } - - var share []string - if gcpUploadOptions.ShareWithAccounts != nil { - share = *gcpUploadOptions.ShareWithAccounts - } - var region string - if gcpUploadOptions.Region != nil { - region = *gcpUploadOptions.Region - } - object := fmt.Sprintf("composer-api-%s", uuid.New().String()) - t := target.NewGCPTarget(&target.GCPTargetOptions{ - Filename: imageType.Filename(), - Region: region, - Os: "", // not exposed in cloudapi for now - Bucket: gcpUploadOptions.Bucket, - Object: object, - ShareWithAccounts: share, - }) - // Import will fail if an image with this name already exists - if gcpUploadOptions.ImageName != nil { - t.ImageName = *gcpUploadOptions.ImageName - } else { - t.ImageName = object - } - - targets = append(targets, t) - } else if uploadRequest.Type == UploadTypes_azure { - var azureUploadOptions AzureUploadRequestOptions - jsonUploadOptions, err := json.Marshal(uploadRequest.Options) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to marshal azure upload request") - } - err = json.Unmarshal(jsonUploadOptions, &azureUploadOptions) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Unable to unmarshal azure upload request") - } - t := target.NewAzureImageTarget(&target.AzureImageTargetOptions{ - Filename: imageType.Filename(), - TenantID: azureUploadOptions.TenantId, - Location: azureUploadOptions.Location, - SubscriptionID: azureUploadOptions.SubscriptionId, - ResourceGroup: azureUploadOptions.ResourceGroup, - }) - - if azureUploadOptions.ImageName != nil { - t.ImageName = *azureUploadOptions.ImageName - } else { - // if ImageName wasn't given, generate a random one - t.ImageName = fmt.Sprintf("composer-api-%s", uuid.New().String()) - } - - targets = append(targets, t) - } else { - return echo.NewHTTPError(http.StatusBadRequest, "Unknown upload request type, only 'aws', 'azure' and 'gcp' are supported") - } - } - - var ir imageRequest - if len(imageRequests) == 1 { - // NOTE: the store currently does not support multi-image composes - ir = imageRequests[0] - } else { - return echo.NewHTTPError(http.StatusBadRequest, "Only single-image composes are currently supported") - } - - id, err := h.server.workers.EnqueueOSBuild(ir.arch, &worker.OSBuildJob{ - Manifest: ir.manifest, - Targets: targets, - Exports: ir.exports, - PipelineNames: &ir.pipelineNames, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to enqueue manifest") - } - - var response ComposeResult - response.Id = id.String() - - return ctx.JSON(http.StatusCreated, response) -} - -// ComposeStatus handles a /compose/{id} GET request -func (h *apiHandlers) ComposeStatus(ctx echo.Context, id string) error { - jobId, err := uuid.Parse(id) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid format for parameter id: %s", err) - } - - var result worker.OSBuildJobResult - status, _, err := h.server.workers.JobStatus(jobId, &result) - if err != nil { - return echo.NewHTTPError(http.StatusNotFound, "Job %s not found: %s", id, err) - } - - var us *UploadStatus - if result.TargetResults != nil { - // Only single upload target is allowed, therefore only a single upload target result is allowed as well - if len(result.TargetResults) != 1 { - return echo.NewHTTPError(http.StatusInternalServerError, "Job %s returned more upload target results than allowed", id) - } - tr := *result.TargetResults[0] - - var uploadType UploadTypes - var uploadOptions interface{} - - switch tr.Name { - case "org.osbuild.aws": - uploadType = UploadTypes_aws - awsOptions := tr.Options.(*target.AWSTargetResultOptions) - uploadOptions = AWSUploadStatus{ - Ami: awsOptions.Ami, - Region: awsOptions.Region, - } - case "org.osbuild.aws.s3": - uploadType = UploadTypes_aws_s3 - awsOptions := tr.Options.(*target.AWSS3TargetResultOptions) - uploadOptions = AWSS3UploadStatus{ - Url: awsOptions.URL, - } - case "org.osbuild.gcp": - uploadType = UploadTypes_gcp - gcpOptions := tr.Options.(*target.GCPTargetResultOptions) - uploadOptions = GCPUploadStatus{ - ImageName: gcpOptions.ImageName, - ProjectId: gcpOptions.ProjectID, - } - case "org.osbuild.azure.image": - uploadType = UploadTypes_azure - gcpOptions := tr.Options.(*target.AzureImageTargetResultOptions) - uploadOptions = AzureUploadStatus{ - ImageName: gcpOptions.ImageName, - } - default: - return echo.NewHTTPError(http.StatusInternalServerError, "Job %s returned unknown upload target results %s", id, tr.Name) - } - - us = &UploadStatus{ - Status: result.UploadStatus, - Type: uploadType, - Options: uploadOptions, - } - } - - response := ComposeStatus{ - ImageStatus: ImageStatus{ - Status: composeStatusFromJobStatus(status, &result), - UploadStatus: us, - }, - } - return ctx.JSON(http.StatusOK, response) -} - -func composeStatusFromJobStatus(js *worker.JobStatus, result *worker.OSBuildJobResult) ImageStatusValue { - if js.Canceled { - return ImageStatusValue_failure - } - - if js.Started.IsZero() { - return ImageStatusValue_pending - } - - if js.Finished.IsZero() { - // TODO: handle also ImageStatusValue_uploading - // TODO: handle also ImageStatusValue_registering - return ImageStatusValue_building - } - - if result.Success { - return ImageStatusValue_success - } - - return ImageStatusValue_failure -} - -// GetOpenapiJson handles a /openapi.json GET request -func (h *apiHandlers) GetOpenapiJson(ctx echo.Context) error { - spec, err := GetSwagger() - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Could not load openapi spec") - } - return ctx.JSON(http.StatusOK, spec) -} - -// GetVersion handles a /version GET request -func (h *apiHandlers) GetVersion(ctx echo.Context) error { - spec, err := GetSwagger() - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Could not load version") - } - version := Version{spec.Info.Version} - return ctx.JSON(http.StatusOK, version) -} - -// ComposeMetadata handles a /compose/{id}/metadata GET request -func (h *apiHandlers) ComposeMetadata(ctx echo.Context, id string) error { - jobId, err := uuid.Parse(id) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid format for parameter id: %s", err) - } - - var result worker.OSBuildJobResult - status, _, err := h.server.workers.JobStatus(jobId, &result) - if err != nil { - return echo.NewHTTPError(http.StatusNotFound, "Job %s not found: %s", id, err) - } - - if result.OSBuildOutput == nil || len(result.OSBuildOutput.Log) == 0 { - // no data to work with; parse error - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to read metadata for job %s", id) - } - - var job worker.OSBuildJob - if _, _, _, err = h.server.workers.Job(jobId, &job); err != nil { - return echo.NewHTTPError(http.StatusNotFound, "Job %s not found: %s", id, err) - } - - if status.Finished.IsZero() { - // job still running: empty response - return ctx.JSON(200, ComposeMetadata{}) - } - - var ostreeCommitMetadata *osbuild.OSTreeCommitStageMetadata - var rpmStagesMd []osbuild.RPMStageMetadata // collect rpm stage metadata from payload pipelines - for _, plName := range job.PipelineNames.Payload { - plMd, hasMd := result.OSBuildOutput.Metadata[plName] - if !hasMd { - continue - } - for _, stageMd := range plMd { - switch md := stageMd.(type) { - case *osbuild.RPMStageMetadata: - rpmStagesMd = append(rpmStagesMd, *md) - case *osbuild.OSTreeCommitStageMetadata: - ostreeCommitMetadata = md - } - } - } - - packages := stagesToPackageMetadata(rpmStagesMd) - - resp := new(ComposeMetadata) - resp.Packages = &packages - - if ostreeCommitMetadata != nil { - resp.OstreeCommit = &ostreeCommitMetadata.Compose.OSTreeCommit - } - - return ctx.JSON(200, resp) -} - -func stagesToPackageMetadata(stages []osbuild.RPMStageMetadata) []PackageMetadata { - packages := make([]PackageMetadata, 0) - for _, md := range stages { - for _, rpm := range md.Packages { - packages = append(packages, - PackageMetadata{ - Type: "rpm", - Name: rpm.Name, - Version: rpm.Version, - Release: rpm.Release, - Epoch: rpm.Epoch, - Arch: rpm.Arch, - Sigmd5: rpm.SigMD5, - Signature: rpmmd.PackageMetadataToSignature(rpm), - }, - ) - } - } - return packages -} diff --git a/osbuild-composer.spec b/osbuild-composer.spec index 60152e853..0480391e4 100644 --- a/osbuild-composer.spec +++ b/osbuild-composer.spec @@ -107,7 +107,6 @@ Obsoletes: osbuild-composer-koji <= 23 # generated code compatible by applying some sed magic. # # Remove when F33 is EOL -sed -i "s/openapi3.Swagger/openapi3.T/;s/openapi3.NewSwaggerLoader().LoadSwaggerFromData/openapi3.NewLoader().LoadFromData/" internal/cloudapi/v1/openapi.v1.gen.go sed -i "s/openapi3.Swagger/openapi3.T/;s/openapi3.NewSwaggerLoader().LoadSwaggerFromData/openapi3.NewLoader().LoadFromData/" internal/cloudapi/v2/openapi.v2.gen.go sed -i "s/openapi3.Swagger/openapi3.T/;s/openapi3.NewSwaggerLoader().LoadSwaggerFromData/openapi3.NewLoader().LoadFromData/" internal/worker/api/api.gen.go %endif