cloudapi: add support for uploading to a container registry

Worker
------
Add configuration for the default container registry.
Use the default container registry if not provided as part
of the image name.
When using the default registry use the configured values
Return the image url as part of the result.

Composer Worker API
-------------------
Add `ContainerTargetResultOptions` to return the image url

Composer API
------------
Add UploadOptions to allow setting of the image name and tag
Add UploadStatus to return the url of the uploaded image

Co-Developed-By: Christian Kellner <christian@kellner.me>
This commit is contained in:
Ygal Blum 2022-07-26 14:45:52 +03:00 committed by Tom Gundersen
parent 14208d872b
commit 3231aabbc0
9 changed files with 277 additions and 130 deletions

View file

@ -52,6 +52,10 @@ type authenticationConfig struct {
type containersConfig struct { type containersConfig struct {
AuthFilePath string `toml:"auth_file_path"` AuthFilePath string `toml:"auth_file_path"`
Domain string `toml:"domain"`
Account string `toml:"account"`
CertPath string `toml:"cert_path"`
TLSVerify bool `toml:"tls_verify"`
} }
type workerConfig struct { type workerConfig struct {

View file

@ -40,16 +40,24 @@ type S3Configuration struct {
SkipSSLVerification bool SkipSSLVerification bool
} }
type OSBuildJobImpl struct { type ContainersConfiguration struct {
Store string
Output string
KojiServers map[string]kojiServer
GCPCreds string
AzureCreds *azure.Credentials
AWSCreds string
AWSBucket string
S3Config S3Configuration
ContainerAuthFile string ContainerAuthFile string
Domain string
Account string
CertPath string
TLSVerify *bool
}
type OSBuildJobImpl struct {
Store string
Output string
KojiServers map[string]kojiServer
GCPCreds string
AzureCreds *azure.Credentials
AWSCreds string
AWSBucket string
S3Config S3Configuration
ContainersConfig ContainersConfiguration
} }
// Returns an *awscloud.AWS object with the credentials of the request. If they // Returns an *awscloud.AWS object with the credentials of the request. If they
@ -199,6 +207,40 @@ func uploadToS3(a *awscloud.AWS, outputDirectory, exportPath, bucket, key, filen
return url, nil return url, nil
} }
func (impl *OSBuildJobImpl) getContainerClient(destination string, targetOptions *target.ContainerTargetOptions) (*container.Client, error) {
useImpl := false
i := strings.IndexRune(destination, '/')
if i == -1 || (!strings.ContainsAny(destination[:i], ".:") && destination[:i] != "localhost") {
if impl.ContainersConfig.Domain != "" {
base := impl.ContainersConfig.Domain
if impl.ContainersConfig.Account != "" {
base = fmt.Sprintf("%s/%s", base, impl.ContainersConfig.Account)
}
destination = fmt.Sprintf("%s/%s", base, destination)
useImpl = true
}
}
client, err := container.NewClient(destination)
if err != nil {
return nil, err
}
if useImpl {
if impl.ContainersConfig.CertPath != "" {
client.SetDockerCertPath(impl.ContainersConfig.CertPath)
}
client.SetTLSVerify(impl.ContainersConfig.TLSVerify)
} else {
if targetOptions.Username != "" || targetOptions.Password != "" {
client.SetCredentials(targetOptions.Username, targetOptions.Password)
}
client.SetTLSVerify(targetOptions.TlsVerify)
}
return client, nil
}
func (impl *OSBuildJobImpl) Run(job worker.Job) error { func (impl *OSBuildJobImpl) Run(job worker.Job) error {
logWithId := logrus.WithField("jobId", job.Id().String()) logWithId := logrus.WithField("jobId", job.Id().String())
// Initialize variable needed for reporting back to osbuild-composer. // Initialize variable needed for reporting back to osbuild-composer.
@ -308,9 +350,9 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
} }
var extraEnv []string var extraEnv []string
if impl.ContainerAuthFile != "" { if impl.ContainersConfig.ContainerAuthFile != "" {
extraEnv = []string{ extraEnv = []string{
fmt.Sprintf("REGISTRY_AUTH_FILE=%s", impl.ContainerAuthFile), fmt.Sprintf("REGISTRY_AUTH_FILE=%s", impl.ContainersConfig.ContainerAuthFile),
} }
} }
@ -775,34 +817,29 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
targetResult.Options = &target.OCITargetResultOptions{ImageID: imageID} targetResult.Options = &target.OCITargetResultOptions{ImageID: imageID}
case *target.ContainerTargetOptions: case *target.ContainerTargetOptions:
targetResult = target.NewContainerTargetResult() targetResult = target.NewContainerTargetResult(nil)
destination := jobTarget.ImageName destination := jobTarget.ImageName
logWithId.Printf("[container] ⬆ Uploading the image to %s", destination) logWithId.Printf("[container] ⬆ Uploading the image to %s", destination)
ctx := context.Background() client, err := impl.getContainerClient(destination, targetOptions)
client, err := container.NewClient(destination)
if err != nil { if err != nil {
targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorInvalidConfig, err.Error()) targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorInvalidConfig, err.Error())
break break
} }
if targetOptions.Username != "" || targetOptions.Password != "" {
client.SetCredentials(targetOptions.Username, targetOptions.Password)
}
client.SetTLSVerify(targetOptions.TlsVerify)
sourcePath := path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename) sourcePath := path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename)
// TODO: get the container type from the metadata of the osbuild job // TODO: get the container type from the metadata of the osbuild job
sourceRef := fmt.Sprintf("oci-archive:%s", sourcePath) sourceRef := fmt.Sprintf("oci-archive:%s", sourcePath)
digest, err := client.UploadImage(ctx, sourceRef, "") digest, err := client.UploadImage(context.Background(), sourceRef, "")
if err != nil { if err != nil {
targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, err.Error()) targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, err.Error())
break break
} }
logWithId.Printf("[container] 🎉 Image uploaded (%s)!", digest.String()) logWithId.Printf("[container] 🎉 Image uploaded (%s)!", digest.String())
targetResult.Options = &target.ContainerTargetResultOptions{URL: client.Target.String(), Digest: digest.String()}
default: default:
// TODO: we may not want to return completely here with multiple targets, because then no TargetErrors will be added to the JobError details // TODO: we may not want to return completely here with multiple targets, because then no TargetErrors will be added to the JobError details

View file

@ -378,8 +378,16 @@ func main() {
} }
var containersAuthFilePath string var containersAuthFilePath string
var containersDomain = ""
var containersAccount = ""
var containersCertPath = ""
var containersTLSVerify = true
if config.Containers != nil { if config.Containers != nil {
containersAuthFilePath = config.Containers.AuthFilePath containersAuthFilePath = config.Containers.AuthFilePath
containersDomain = config.Containers.Domain
containersAccount = config.Containers.Account
containersCertPath = config.Containers.CertPath
containersTLSVerify = config.Containers.TLSVerify
} }
// depsolve jobs can be done during other jobs // depsolve jobs can be done during other jobs
@ -435,7 +443,13 @@ func main() {
CABundle: genericS3CABundle, CABundle: genericS3CABundle,
SkipSSLVerification: genericS3SkipSSLVerification, SkipSSLVerification: genericS3SkipSSLVerification,
}, },
ContainerAuthFile: containersAuthFilePath, ContainersConfig: ContainersConfiguration{
ContainerAuthFile: containersAuthFilePath,
Domain: containersDomain,
Account: containersAccount,
CertPath: containersCertPath,
TLSVerify: &containersTLSVerify,
},
}, },
worker.JobTypeKojiInit: &KojiInitJobImpl{ worker.JobTypeKojiInit: &KojiInitJobImpl{
KojiServers: kojiServers, KojiServers: kojiServers,

View file

@ -344,8 +344,6 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
fallthrough fallthrough
case ImageTypesEdgeInstaller: case ImageTypesEdgeInstaller:
fallthrough fallthrough
case ImageTypesEdgeContainer:
fallthrough
case ImageTypesEdgeCommit: case ImageTypesEdgeCommit:
var awsS3UploadOptions AWSS3UploadOptions var awsS3UploadOptions AWSS3UploadOptions
jsonUploadOptions, err := json.Marshal(*ir.UploadOptions) jsonUploadOptions, err := json.Marshal(*ir.UploadOptions)
@ -365,6 +363,22 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
t.ImageName = key t.ImageName = key
t.OsbuildArtifact.ExportFilename = imageType.Filename() t.OsbuildArtifact.ExportFilename = imageType.Filename()
irTarget = t
case ImageTypesEdgeContainer:
var containerUploadOptions ContainerUploadOptions
jsonUploadOptions, err := json.Marshal(*ir.UploadOptions)
if err != nil {
return HTTPError(ErrorJSONMarshallingError)
}
err = json.Unmarshal(jsonUploadOptions, &containerUploadOptions)
if err != nil {
return HTTPError(ErrorJSONUnMarshallingError)
}
t := target.NewContainerTarget(&target.ContainerTargetOptions{})
t.ImageName = fmt.Sprintf("%s:%s", containerUploadOptions.Name, containerUploadOptions.Tag)
t.OsbuildArtifact.ExportFilename = imageType.Filename()
irTarget = t irTarget = t
case ImageTypesGcp: case ImageTypesGcp:
fallthrough fallthrough
@ -544,6 +558,14 @@ func targetResultToUploadStatus(t *target.TargetResult) (*UploadStatus, error) {
uploadOptions = AzureUploadStatus{ uploadOptions = AzureUploadStatus{
ImageName: gcpOptions.ImageName, ImageName: gcpOptions.ImageName,
} }
case target.TargetNameContainer:
uploadType = UploadTypesContainer
containerOptions := t.Options.(*target.ContainerTargetResultOptions)
uploadOptions = ContainerUploadStatus{
Url: containerOptions.URL,
Digest: containerOptions.Digest,
}
default: default:
return nil, fmt.Errorf("unknown upload target: %s", t.Name) return nil, fmt.Errorf("unknown upload target: %s", t.Name)
} }

View file

@ -96,6 +96,8 @@ const (
UploadTypesAzure UploadTypes = "azure" UploadTypesAzure UploadTypes = "azure"
UploadTypesContainer UploadTypes = "container"
UploadTypesGcp UploadTypes = "gcp" UploadTypesGcp UploadTypes = "gcp"
) )
@ -218,6 +220,24 @@ type ComposeStatusError struct {
// ComposeStatusValue defines model for ComposeStatusValue. // ComposeStatusValue defines model for ComposeStatusValue.
type ComposeStatusValue string type ComposeStatusValue string
// ContainerUploadOptions defines model for ContainerUploadOptions.
type ContainerUploadOptions struct {
// Name for the created container image
Name string `json:"name"`
// Tag for the created container image
Tag string `json:"tag"`
}
// ContainerUploadStatus defines model for ContainerUploadStatus.
type ContainerUploadStatus struct {
// Digest of the manifest of the uploaded container on the registry
Digest string `json:"digest"`
// FQDN of the uploaded image
Url string `json:"url"`
}
// Customizations defines model for Customizations. // Customizations defines model for Customizations.
type Customizations struct { type Customizations struct {
Filesystem *[]Filesystem `json:"filesystem,omitempty"` Filesystem *[]Filesystem `json:"filesystem,omitempty"`
@ -672,110 +692,112 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object // Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{ var swaggerSpec = []string{
"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",
} }
// GetSwagger returns the content of the embedded swagger specification file // GetSwagger returns the content of the embedded swagger specification file

View file

@ -511,6 +511,7 @@ components:
- $ref: '#/components/schemas/AWSS3UploadStatus' - $ref: '#/components/schemas/AWSS3UploadStatus'
- $ref: '#/components/schemas/GCPUploadStatus' - $ref: '#/components/schemas/GCPUploadStatus'
- $ref: '#/components/schemas/AzureUploadStatus' - $ref: '#/components/schemas/AzureUploadStatus'
- $ref: '#/components/schemas/ContainerUploadStatus'
UploadStatusValue: UploadStatusValue:
type: string type: string
enum: ['success', 'failure', 'pending', 'running'] enum: ['success', 'failure', 'pending', 'running']
@ -521,6 +522,7 @@ components:
- aws.s3 - aws.s3
- gcp - gcp
- azure - azure
- container
AWSEC2UploadStatus: AWSEC2UploadStatus:
type: object type: object
required: required:
@ -566,7 +568,22 @@ components:
build_id: build_id:
type: integer type: integer
example: 42 example: 42
ContainerUploadStatus:
type: object
additionalProperties: false
required:
- url
- digest
properties:
url:
type: string
example: 'quay.io/myaccount/osbuild:latest'
description: |
FQDN of the uploaded image
digest:
type: string
description: |
Digest of the manifest of the uploaded container on the registry
ComposeMetadata: ComposeMetadata:
allOf: allOf:
- $ref: '#/components/schemas/ObjectReference' - $ref: '#/components/schemas/ObjectReference'
@ -700,6 +717,7 @@ components:
- $ref: '#/components/schemas/AWSS3UploadOptions' - $ref: '#/components/schemas/AWSS3UploadOptions'
- $ref: '#/components/schemas/GCPUploadOptions' - $ref: '#/components/schemas/GCPUploadOptions'
- $ref: '#/components/schemas/AzureUploadOptions' - $ref: '#/components/schemas/AzureUploadOptions'
- $ref: '#/components/schemas/ContainerUploadOptions'
description: | description: |
This should really be oneOf but AWSS3UploadOptions is a subset of This should really be oneOf but AWSS3UploadOptions is a subset of
AWSEC2UploadOptions. This means that all AWSEC2UploadOptions objects AWSEC2UploadOptions. This means that all AWSEC2UploadOptions objects
@ -824,6 +842,23 @@ components:
Name of the uploaded image. It must be unique in the given resource group. 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 If name is omitted from the request, a random one based on a UUID is
generated. generated.
ContainerUploadOptions:
type: object
additionalProperties: false
required:
- name
- tag
properties:
name:
type: string
example: 'osbuild'
description: |
Name for the created container image
tag:
type: string
example: 'latest'
description: |
Tag for the created container image
Customizations: Customizations:
type: object type: object
properties: properties:

View file

@ -194,6 +194,10 @@ func (cl *Client) SetCredentials(username, password string) {
cl.sysCtx.DockerAuthConfig.Password = password cl.sysCtx.DockerAuthConfig.Password = password
} }
func (cl *Client) SetDockerCertPath(path string) {
cl.sysCtx.DockerCertPath = path
}
// SetSkipTLSVerify controls if TLS verification happens when // SetSkipTLSVerify controls if TLS verification happens when
// making requests. If nil is passed it falls back to the default. // making requests. If nil is passed it falls back to the default.
func (cl *Client) SetTLSVerify(verify *bool) { func (cl *Client) SetTLSVerify(verify *bool) {

View file

@ -17,6 +17,13 @@ func NewContainerTarget(options *ContainerTargetOptions) *Target {
return newTarget(TargetNameContainer, options) return newTarget(TargetNameContainer, options)
} }
func NewContainerTargetResult() *TargetResult { type ContainerTargetResultOptions struct {
return newTargetResult(TargetNameContainer, nil) URL string `json:"url"`
Digest string `json:"digest"`
}
func (ContainerTargetResultOptions) isTargetResultOptions() {}
func NewContainerTargetResult(options *ContainerTargetResultOptions) *TargetResult {
return newTargetResult(TargetNameContainer, options)
} }

View file

@ -67,6 +67,8 @@ func UnmarshalTargetResultOptions(trName TargetName, rawOptions json.RawMessage)
options = new(KojiTargetResultOptions) options = new(KojiTargetResultOptions)
case TargetNameOCI: case TargetNameOCI:
options = new(OCITargetResultOptions) options = new(OCITargetResultOptions)
case TargetNameContainer:
options = new(ContainerTargetResultOptions)
default: default:
return nil, fmt.Errorf("unexpected target result name: %s", trName) return nil, fmt.Errorf("unexpected target result name: %s", trName)
} }