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 {
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 {

View file

@ -40,16 +40,24 @@ type S3Configuration struct {
SkipSSLVerification bool
}
type OSBuildJobImpl struct {
Store string
Output string
KojiServers map[string]kojiServer
GCPCreds string
AzureCreds *azure.Credentials
AWSCreds string
AWSBucket string
S3Config S3Configuration
type ContainersConfiguration struct {
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
@ -199,6 +207,40 @@ func uploadToS3(a *awscloud.AWS, outputDirectory, exportPath, bucket, key, filen
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 {
logWithId := logrus.WithField("jobId", job.Id().String())
// Initialize variable needed for reporting back to osbuild-composer.
@ -308,9 +350,9 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
}
var extraEnv []string
if impl.ContainerAuthFile != "" {
if impl.ContainersConfig.ContainerAuthFile != "" {
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}
case *target.ContainerTargetOptions:
targetResult = target.NewContainerTargetResult()
targetResult = target.NewContainerTargetResult(nil)
destination := jobTarget.ImageName
logWithId.Printf("[container] ⬆ Uploading the image to %s", destination)
ctx := context.Background()
client, err := container.NewClient(destination)
client, err := impl.getContainerClient(destination, targetOptions)
if err != nil {
targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorInvalidConfig, err.Error())
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)
// TODO: get the container type from the metadata of the osbuild job
sourceRef := fmt.Sprintf("oci-archive:%s", sourcePath)
digest, err := client.UploadImage(ctx, sourceRef, "")
digest, err := client.UploadImage(context.Background(), sourceRef, "")
if err != nil {
targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, err.Error())
break
}
logWithId.Printf("[container] 🎉 Image uploaded (%s)!", digest.String())
targetResult.Options = &target.ContainerTargetResultOptions{URL: client.Target.String(), Digest: digest.String()}
default:
// 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 containersDomain = ""
var containersAccount = ""
var containersCertPath = ""
var containersTLSVerify = true
if config.Containers != nil {
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
@ -435,7 +443,13 @@ func main() {
CABundle: genericS3CABundle,
SkipSSLVerification: genericS3SkipSSLVerification,
},
ContainerAuthFile: containersAuthFilePath,
ContainersConfig: ContainersConfiguration{
ContainerAuthFile: containersAuthFilePath,
Domain: containersDomain,
Account: containersAccount,
CertPath: containersCertPath,
TLSVerify: &containersTLSVerify,
},
},
worker.JobTypeKojiInit: &KojiInitJobImpl{
KojiServers: kojiServers,