cloudfoundry-incubator/eirini

View on GitHub
bifrost/convert.go

Summary

Maintainability
A
1 hr
Test Coverage
A
95%
package bifrost

import (
    "fmt"

    "code.cloudfoundry.org/eirini"
    "code.cloudfoundry.org/eirini/api"
    "code.cloudfoundry.org/eirini/models/cf"
    "code.cloudfoundry.org/eirini/util"
    "code.cloudfoundry.org/lager"
    "github.com/pkg/errors"
)

type lifecycleOptions struct {
    command         []string
    env             map[string]string
    image           string
    privateRegistry *api.PrivateRegistry
}

type APIConverter struct {
    logger lager.Logger
}

func NewAPIConverter(logger lager.Logger) *APIConverter {
    return &APIConverter{
        logger: logger,
    }
}

func (c *APIConverter) ConvertLRP(request cf.DesireLRPRequest) (api.LRP, error) {
    env := map[string]string{
        "LANG": "en_US.UTF-8",
    }

    var port int32
    if len(request.Ports) != 0 {
        port = request.Ports[0]
        env[eirini.EnvCFInstanceAddr] = fmt.Sprintf("0.0.0.0:%d", port)
        env[eirini.EnvCFInstancePort] = fmt.Sprintf("%d", port)
        env[eirini.EnvCFInstancePorts] = fmt.Sprintf(`[{"external":%d,"internal":%d}]`, port, port)
    }

    healthcheck := api.Healthcheck{
        Type:      request.HealthCheckType,
        Endpoint:  request.HealthCheckHTTPEndpoint,
        TimeoutMs: request.HealthCheckTimeoutMs,
        Port:      port,
    }

    lrpLifecycleOptions, err := c.getLifecycleOptions(request)
    if err != nil {
        return api.LRP{}, err
    }

    identifier := api.LRPIdentifier{
        GUID:    request.GUID,
        Version: request.Version,
    }

    err = c.validateRequest(request)
    if err != nil {
        return api.LRP{}, err
    }

    return api.LRP{
        AppName:                request.AppName,
        AppGUID:                request.AppGUID,
        LastUpdated:            request.LastUpdated,
        OrgName:                request.OrganizationName,
        OrgGUID:                request.OrganizationGUID,
        SpaceName:              request.SpaceName,
        SpaceGUID:              request.SpaceGUID,
        LRPIdentifier:          identifier,
        ProcessType:            request.ProcessType,
        Image:                  lrpLifecycleOptions.image,
        TargetInstances:        request.NumInstances,
        Command:                lrpLifecycleOptions.command,
        Env:                    mergeMaps(request.Environment, env, lrpLifecycleOptions.env),
        Health:                 healthcheck,
        Ports:                  request.Ports,
        MemoryMB:               request.MemoryMB,
        DiskMB:                 request.DiskMB,
        CPUWeight:              request.CPUWeight,
        VolumeMounts:           convertVolumeMounts(request),
        LRP:                    request.LRP,
        UserDefinedAnnotations: request.UserDefinedAnnotations,
        PrivateRegistry:        lrpLifecycleOptions.privateRegistry,
    }, nil
}

func (c *APIConverter) ConvertTask(taskGUID string, request cf.TaskRequest) (api.Task, error) {
    c.logger.Debug("convert-task", lager.Data{"app-id": request.AppGUID, "task-guid": taskGUID})

    env := map[string]string{
        "HOME":   "/home/vcap/app",
        "PATH":   "/usr/local/bin:/usr/bin:/bin",
        "USER":   "vcap",
        "TMPDIR": "/home/vcap/tmp",
    }

    task := api.Task{
        GUID:               taskGUID,
        Name:               request.Name,
        CompletionCallback: request.CompletionCallback,
        AppName:            request.AppName,
        AppGUID:            request.AppGUID,
        OrgName:            request.OrgName,
        SpaceName:          request.SpaceName,
        OrgGUID:            request.OrgGUID,
        SpaceGUID:          request.SpaceGUID,
    }

    if request.Lifecycle.DockerLifecycle == nil {
        return api.Task{}, errors.New("docker is the only supported lifecycle")
    }

    lifecycle := request.Lifecycle.DockerLifecycle
    task.Command = lifecycle.Command
    task.Image = lifecycle.Image

    if lifecycle.RegistryUsername != "" || lifecycle.RegistryPassword != "" {
        task.PrivateRegistry = &api.PrivateRegistry{
            Server:   util.ParseImageRegistryHost(lifecycle.Image),
            Username: lifecycle.RegistryUsername,
            Password: lifecycle.RegistryPassword,
        }
    }

    task.Env = mergeEnvs(request.Environment, env)

    return task, nil
}

func mergeMaps(maps ...map[string]string) map[string]string {
    result := make(map[string]string)

    for _, m := range maps {
        for k, v := range m {
            result[k] = v
        }
    }

    return result
}

func mergeEnvs(requestEnv []cf.EnvironmentVariable, appliedEnv map[string]string) map[string]string {
    result := make(map[string]string)
    for _, v := range requestEnv {
        result[v.Name] = v.Value
    }

    for k, v := range appliedEnv {
        result[k] = v
    }

    return result
}

func (c *APIConverter) getLifecycleOptions(request cf.DesireLRPRequest) (*lifecycleOptions, error) {
    options := &lifecycleOptions{}

    if request.Lifecycle.DockerLifecycle == nil {
        return nil, fmt.Errorf("missing lifecycle data")
    }

    var err error

    lifecycle := request.Lifecycle.DockerLifecycle
    options.image = lifecycle.Image
    options.command = lifecycle.Command

    if err != nil {
        return nil, errors.Wrap(err, "failed to verify if docker image needs root user")
    }

    registryUsername := lifecycle.RegistryUsername
    registryPassword := lifecycle.RegistryPassword

    if registryUsername != "" || registryPassword != "" {
        options.privateRegistry = &api.PrivateRegistry{
            Server:   util.ParseImageRegistryHost(options.image),
            Username: registryUsername,
            Password: registryPassword,
        }
    }

    return options, nil
}

func convertVolumeMounts(request cf.DesireLRPRequest) []api.VolumeMount {
    volumeMounts := []api.VolumeMount{}
    for _, vm := range request.VolumeMounts {
        volumeMounts = append(volumeMounts, api.VolumeMount{
            MountPath: vm.MountDir,
            ClaimName: vm.VolumeID,
        })
    }

    return volumeMounts
}

func (c *APIConverter) validateRequest(request cf.DesireLRPRequest) error {
    if request.DiskMB == 0 {
        return errors.New("DiskMB cannot be 0")
    }

    return nil
}