cloudfoundry-incubator/eirini

View on GitHub
k8s/jobs/desire.go

Summary

Maintainability
A
2 hrs
Test Coverage
A
97%
package jobs

import (
    "context"

    "code.cloudfoundry.org/eirini/api"
    "code.cloudfoundry.org/eirini/k8s/shared"
    "code.cloudfoundry.org/eirini/k8s/utils/dockerutils"
    "code.cloudfoundry.org/lager"
    "github.com/hashicorp/go-multierror"
    "github.com/pkg/errors"
    batch "k8s.io/api/batch/v1"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

//counterfeiter:generate . TaskToJobConverter
//counterfeiter:generate . JobCreator
//counterfeiter:generate . SecretsClient

type TaskToJobConverter interface {
    Convert(*api.Task, *corev1.Secret) *batch.Job
}

type JobCreator interface {
    Create(ctx context.Context, namespace string, job *batch.Job) (*batch.Job, error)
}

type SecretsClient interface {
    Create(ctx context.Context, namespace string, secret *corev1.Secret) (*corev1.Secret, error)
    SetOwner(ctx context.Context, secret *corev1.Secret, owner metav1.Object) (*corev1.Secret, error)
    Delete(ctx context.Context, namespace string, name string) error
}

type Desirer struct {
    logger             lager.Logger
    taskToJobConverter TaskToJobConverter
    jobCreator         JobCreator
    secrets            SecretsClient
}

func NewDesirer(
    logger lager.Logger,
    taskToJobConverter TaskToJobConverter,
    jobCreator JobCreator,
    secretCreator SecretsClient,
) Desirer {
    return Desirer{
        logger:             logger,
        taskToJobConverter: taskToJobConverter,
        jobCreator:         jobCreator,
        secrets:            secretCreator,
    }
}

func (d *Desirer) Desire(ctx context.Context, namespace string, task *api.Task, opts ...shared.Option) error {
    logger := d.logger.Session("desire-task", lager.Data{"guid": task.GUID, "name": task.Name, "namespace": namespace})

    var (
        err                   error
        privateRegistrySecret *corev1.Secret
    )

    if imageInPrivateRegistry(task) {
        privateRegistrySecret, err = d.createPrivateRegistrySecret(ctx, namespace, task)
        if err != nil {
            return errors.Wrap(err, "failed to create task secret")
        }
    }

    job := d.taskToJobConverter.Convert(task, privateRegistrySecret)

    job.Namespace = namespace

    if err = shared.ApplyOpts(job, opts...); err != nil {
        logger.Error("failed-to-apply-option", err)

        return err
    }

    job, err = d.jobCreator.Create(ctx, namespace, job)
    if err != nil {
        logger.Error("failed-to-create-job", err)

        return d.cleanupAndError(ctx, err, privateRegistrySecret)
    }

    if privateRegistrySecret != nil {
        _, err = d.secrets.SetOwner(ctx, privateRegistrySecret, job)
        if err != nil {
            return errors.Wrap(err, "failed-to-set-secret-ownership")
        }
    }

    return nil
}

func imageInPrivateRegistry(task *api.Task) bool {
    return task.PrivateRegistry != nil && task.PrivateRegistry.Username != "" && task.PrivateRegistry.Password != ""
}

func (d *Desirer) createPrivateRegistrySecret(ctx context.Context, namespace string, task *api.Task) (*corev1.Secret, error) {
    secret := &corev1.Secret{}

    secret.GenerateName = PrivateRegistrySecretGenerateName
    secret.Type = corev1.SecretTypeDockerConfigJson

    dockerConfig := dockerutils.NewDockerConfig(
        task.PrivateRegistry.Server,
        task.PrivateRegistry.Username,
        task.PrivateRegistry.Password,
    )

    dockerConfigJSON, err := dockerConfig.JSON()
    if err != nil {
        return nil, errors.Wrap(err, "failed-to-get-docker-config")
    }

    secret.StringData = map[string]string{
        dockerutils.DockerConfigKey: dockerConfigJSON,
    }

    return d.secrets.Create(ctx, namespace, secret)
}

func (d *Desirer) cleanupAndError(ctx context.Context, jobCreationError error, privateRegistrySecret *corev1.Secret) error {
    resultError := multierror.Append(nil, jobCreationError)

    if privateRegistrySecret != nil {
        err := d.secrets.Delete(ctx, privateRegistrySecret.Namespace, privateRegistrySecret.Name)
        if err != nil {
            resultError = multierror.Append(resultError, errors.Wrap(err, "failed to cleanup registry secret"))
        }
    }

    return resultError
}