api/cloud: drop v1 API
It's deprecated and not used anywhere, let's just drop it. Signed-off-by: Ondřej Budai <ondrej@budai.cz>
This commit is contained in:
parent
8d81da7d7b
commit
d967790ea5
6 changed files with 0 additions and 2277 deletions
|
|
@ -247,7 +247,6 @@ func (c *Composer) Start() error {
|
||||||
|
|
||||||
if c.apiListener != nil {
|
if c.apiListener != nil {
|
||||||
go func() {
|
go func() {
|
||||||
const apiRoute = "/api/composer/v1"
|
|
||||||
const apiRouteV2 = "/api/image-builder-composer/v2"
|
const apiRouteV2 = "/api/image-builder-composer/v2"
|
||||||
const kojiRoute = "/api/composer-koji/v1"
|
const kojiRoute = "/api/composer-koji/v1"
|
||||||
|
|
||||||
|
|
@ -256,7 +255,6 @@ func (c *Composer) Start() error {
|
||||||
// Add a "/" here, because http.ServeMux expects the
|
// Add a "/" here, because http.ServeMux expects the
|
||||||
// trailing slash for rooted subtrees, whereas the
|
// trailing slash for rooted subtrees, whereas the
|
||||||
// handler functions don't.
|
// handler functions don't.
|
||||||
mux.Handle(apiRoute+"/", c.api.V1(apiRoute))
|
|
||||||
mux.Handle(apiRouteV2+"/", c.api.V2(apiRouteV2))
|
mux.Handle(apiRouteV2+"/", c.api.V2(apiRouteV2))
|
||||||
mux.Handle(kojiRoute+"/", c.koji.Handler(kojiRoute))
|
mux.Handle(kojiRoute+"/", c.koji.Handler(kojiRoute))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,27 +7,20 @@ import (
|
||||||
"github.com/osbuild/osbuild-composer/internal/rpmmd"
|
"github.com/osbuild/osbuild-composer/internal/rpmmd"
|
||||||
"github.com/osbuild/osbuild-composer/internal/worker"
|
"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"
|
v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
v1 *v1.Server
|
|
||||||
v2 *v2.Server
|
v2 *v2.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(workers *worker.Server, rpmMetadata rpmmd.RPMMD, distros *distroregistry.Registry, awsBucket string) *Server {
|
func NewServer(workers *worker.Server, rpmMetadata rpmmd.RPMMD, distros *distroregistry.Registry, awsBucket string) *Server {
|
||||||
server := &Server{
|
server := &Server{
|
||||||
v1: v1.NewServer(workers, rpmMetadata, distros),
|
|
||||||
v2: v2.NewServer(workers, rpmMetadata, distros, awsBucket),
|
v2: v2.NewServer(workers, rpmMetadata, distros, awsBucket),
|
||||||
}
|
}
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) V1(path string) http.Handler {
|
|
||||||
return server.v1.Handler(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (server *Server) V2(path string) http.Handler {
|
func (server *Server) V2(path string) http.Handler {
|
||||||
return server.v2.Handler(path)
|
return server.v2.Handler(path)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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-<uuid>' 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"
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -107,7 +107,6 @@ Obsoletes: osbuild-composer-koji <= 23
|
||||||
# generated code compatible by applying some sed magic.
|
# generated code compatible by applying some sed magic.
|
||||||
#
|
#
|
||||||
# Remove when F33 is EOL
|
# 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/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
|
sed -i "s/openapi3.Swagger/openapi3.T/;s/openapi3.NewSwaggerLoader().LoadSwaggerFromData/openapi3.NewLoader().LoadFromData/" internal/worker/api/api.gen.go
|
||||||
%endif
|
%endif
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue