forked from LaconicNetwork/kompose
feat: migrate from libcompose to compose-go (#1547)
* feat: migrate from libcompose to compose-go libcompose has been deprecated since summer 2021 in favor of https://github.com/compose-spec/compose-go. Kompose should now be able to load all versions of compose. * chore: replace golint with staticcheck golint has been deprecated. Recommended replacement is staticcheck.
This commit is contained in:
+733
-55
@@ -18,30 +18,36 @@ package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/fatih/structs"
|
||||
"github.com/google/shlex"
|
||||
"github.com/kubernetes/kompose/pkg/kobject"
|
||||
"github.com/kubernetes/kompose/pkg/transformer"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cast"
|
||||
api "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
//StdinData is data bytes read from stdin
|
||||
// StdinData is data bytes read from stdin
|
||||
var StdinData []byte
|
||||
|
||||
// Compose is docker compose file loader, implements Loader interface
|
||||
type Compose struct {
|
||||
}
|
||||
|
||||
// checkUnsupportedKey checks if libcompose project contains
|
||||
// checkUnsupportedKey checks if compose-go project contains
|
||||
// keys that are not supported by this loader.
|
||||
// list of all unsupported keys are stored in unsupportedKey variable
|
||||
// returns list of unsupported YAML keys from docker-compose
|
||||
func checkUnsupportedKey(composeProject *project.Project) []string {
|
||||
func checkUnsupportedKey(composeProject *types.Project) []string {
|
||||
// list of all unsupported keys for this loader
|
||||
// this is map to make searching for keys easier
|
||||
// to make sure that unsupported key is not going to be reported twice
|
||||
@@ -79,18 +85,18 @@ func checkUnsupportedKey(composeProject *project.Project) []string {
|
||||
|
||||
// Root level keys are not yet supported except Network
|
||||
// Check to see if the default network is available and length is only equal to one.
|
||||
if _, ok := composeProject.NetworkConfigs["default"]; ok && len(composeProject.NetworkConfigs) == 1 {
|
||||
if _, ok := composeProject.Networks["default"]; ok && len(composeProject.Networks) == 1 {
|
||||
log.Debug("Default network found")
|
||||
}
|
||||
|
||||
// Root level volumes are not yet supported
|
||||
if len(composeProject.VolumeConfigs) > 0 {
|
||||
if len(composeProject.Volumes) > 0 {
|
||||
keysFound = append(keysFound, "root level volumes")
|
||||
}
|
||||
|
||||
for _, serviceConfig := range composeProject.ServiceConfigs.All() {
|
||||
for _, serviceConfig := range composeProject.AllServices() {
|
||||
// this reflection is used in check for empty arrays
|
||||
val := reflect.ValueOf(serviceConfig).Elem()
|
||||
val := reflect.ValueOf(serviceConfig)
|
||||
s := structs.New(serviceConfig)
|
||||
|
||||
for _, f := range s.Fields() {
|
||||
@@ -109,7 +115,7 @@ func checkUnsupportedKey(composeProject *project.Project) []string {
|
||||
yamlTagName := strings.Split(f.Tag("yaml"), ",")[0]
|
||||
if f.Name() == "Networks" {
|
||||
// networks always contains one default element, even it isn't declared in compose v2.
|
||||
if len(serviceConfig.Networks.Networks) == 1 && serviceConfig.Networks.Networks[0].Name == "default" {
|
||||
if len(serviceConfig.Networks) == 1 && serviceConfig.NetworksByPriority()[0] == "default" {
|
||||
// this is empty Network definition, skip it
|
||||
continue
|
||||
}
|
||||
@@ -144,61 +150,733 @@ func checkUnsupportedKey(composeProject *project.Project) []string {
|
||||
|
||||
// LoadFile loads a compose file into KomposeObject
|
||||
func (c *Compose) LoadFile(files []string) (kobject.KomposeObject, error) {
|
||||
// Load the json / yaml file in order to get the version value
|
||||
var version string
|
||||
|
||||
for _, file := range files {
|
||||
composeVersion, err := getVersionFromFile(file)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(err, "Unable to load yaml/json file for version parsing")
|
||||
}
|
||||
|
||||
// Check that the previous file loaded matches.
|
||||
if len(files) > 0 && version != "" && version != composeVersion {
|
||||
return kobject.KomposeObject{}, errors.New("All Docker Compose files must be of the same version")
|
||||
}
|
||||
version = composeVersion
|
||||
// Gather the working directory
|
||||
workingDir, err := getComposeFileDir(files)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, err
|
||||
}
|
||||
|
||||
log.Debugf("Docker Compose version: %s", version)
|
||||
projectOptions, err := cli.NewProjectOptions(files, cli.WithOsEnv, cli.WithWorkingDirectory(workingDir), cli.WithInterpolation(false))
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(err, "Unable to create compose options")
|
||||
}
|
||||
|
||||
// Convert based on version
|
||||
switch version {
|
||||
// Use libcompose for 1 or 2
|
||||
// If blank, it's assumed it's 1 or 2
|
||||
case "", "1", "1.0", "2", "2.0", "2.1", "2.2":
|
||||
komposeObject, err := parseV1V2(files)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, err
|
||||
project, err := cli.ProjectFromOptions(projectOptions)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(err, "Unable to load files")
|
||||
}
|
||||
|
||||
komposeObject, err := dockerComposeToKomposeMapping(project)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, err
|
||||
}
|
||||
return komposeObject, nil
|
||||
}
|
||||
|
||||
func loadPlacement(placement types.Placement) kobject.Placement {
|
||||
komposePlacement := kobject.Placement{
|
||||
PositiveConstraints: make(map[string]string),
|
||||
NegativeConstraints: make(map[string]string),
|
||||
Preferences: make([]string, 0, len(placement.Preferences)),
|
||||
}
|
||||
|
||||
// Convert constraints
|
||||
equal, notEqual := " == ", " != "
|
||||
for _, j := range placement.Constraints {
|
||||
operator := equal
|
||||
if strings.Contains(j, notEqual) {
|
||||
operator = notEqual
|
||||
}
|
||||
return komposeObject, nil
|
||||
// Use docker/cli for 3
|
||||
case "3", "3.0", "3.1", "3.2", "3.3", "3.4", "3.5", "3.6", "3.7", "3.8":
|
||||
komposeObject, err := parseV3(files)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, err
|
||||
p := strings.Split(j, operator)
|
||||
if len(p) < 2 {
|
||||
log.Warnf("Failed to parse placement constraints %s, the correct format is 'label == xxx'", j)
|
||||
continue
|
||||
}
|
||||
return komposeObject, nil
|
||||
|
||||
key, err := convertDockerLabel(p[0])
|
||||
if err != nil {
|
||||
log.Warn("Ignore placement constraints: ", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if operator == equal {
|
||||
komposePlacement.PositiveConstraints[key] = p[1]
|
||||
} else if operator == notEqual {
|
||||
komposePlacement.NegativeConstraints[key] = p[1]
|
||||
}
|
||||
}
|
||||
|
||||
// Convert preferences
|
||||
for _, p := range placement.Preferences {
|
||||
// Spread is the only supported strategy currently
|
||||
label, err := convertDockerLabel(p.Spread)
|
||||
if err != nil {
|
||||
log.Warn("Ignore placement preferences: ", err.Error())
|
||||
continue
|
||||
}
|
||||
komposePlacement.Preferences = append(komposePlacement.Preferences, label)
|
||||
}
|
||||
return komposePlacement
|
||||
}
|
||||
|
||||
// Convert docker label to k8s label
|
||||
func convertDockerLabel(dockerLabel string) (string, error) {
|
||||
switch dockerLabel {
|
||||
case "node.hostname":
|
||||
return "kubernetes.io/hostname", nil
|
||||
case "engine.labels.operatingsystem":
|
||||
return "kubernetes.io/os", nil
|
||||
default:
|
||||
return kobject.KomposeObject{}, fmt.Errorf("version %s of Docker Compose is not supported. Please use version 1, 2 or 3", version)
|
||||
if strings.HasPrefix(dockerLabel, "node.labels.") {
|
||||
return strings.TrimPrefix(dockerLabel, "node.labels."), nil
|
||||
}
|
||||
}
|
||||
errMsg := fmt.Sprint(dockerLabel, " is not supported, only 'node.hostname', 'engine.labels.operatingsystem' and 'node.labels.xxx' (ex: node.labels.something == anything) is supported")
|
||||
return "", errors.New(errMsg)
|
||||
}
|
||||
|
||||
// Convert the Docker Compose volumes to []string (the old way)
|
||||
// TODO: Check to see if it's a "bind" or "volume". Ignore for now.
|
||||
// TODO: Refactor it similar to loadPorts
|
||||
// See: https://docs.docker.com/compose/compose-file/#long-syntax-3
|
||||
func loadVolumes(volumes []types.ServiceVolumeConfig) []string {
|
||||
var volArray []string
|
||||
for _, vol := range volumes {
|
||||
// There will *always* be Source when parsing
|
||||
v := vol.Source
|
||||
|
||||
if vol.Target != "" {
|
||||
v = v + ":" + vol.Target
|
||||
}
|
||||
|
||||
if vol.ReadOnly {
|
||||
v = v + ":ro"
|
||||
}
|
||||
|
||||
volArray = append(volArray, v)
|
||||
}
|
||||
return volArray
|
||||
}
|
||||
|
||||
// Convert Docker Compose ports to kobject.Ports
|
||||
// expose ports will be treated as TCP ports
|
||||
func loadPorts(ports []types.ServicePortConfig, expose []string) []kobject.Ports {
|
||||
komposePorts := []kobject.Ports{}
|
||||
exist := map[string]bool{}
|
||||
|
||||
for _, port := range ports {
|
||||
// Convert to a kobject struct with ports
|
||||
komposePorts = append(komposePorts, kobject.Ports{
|
||||
HostPort: cast.ToInt32(port.Published),
|
||||
ContainerPort: int32(port.Target),
|
||||
HostIP: port.HostIP,
|
||||
Protocol: strings.ToUpper(port.Protocol),
|
||||
})
|
||||
exist[cast.ToString(port.Target)+port.Protocol] = true
|
||||
}
|
||||
|
||||
for _, port := range expose {
|
||||
portValue := port
|
||||
protocol := string(api.ProtocolTCP)
|
||||
if strings.Contains(portValue, "/") {
|
||||
splits := strings.Split(port, "/")
|
||||
portValue = splits[0]
|
||||
protocol = splits[1]
|
||||
}
|
||||
|
||||
if exist[portValue+protocol] {
|
||||
continue
|
||||
}
|
||||
komposePorts = append(komposePorts, kobject.Ports{
|
||||
HostPort: cast.ToInt32(portValue),
|
||||
ContainerPort: cast.ToInt32(portValue),
|
||||
HostIP: "",
|
||||
Protocol: strings.ToUpper(protocol),
|
||||
})
|
||||
}
|
||||
|
||||
return komposePorts
|
||||
}
|
||||
|
||||
/*
|
||||
Convert the HealthCheckConfig as designed by Docker to
|
||||
|
||||
a Kubernetes-compatible format.
|
||||
*/
|
||||
func parseHealthCheckReadiness(labels types.Labels) (kobject.HealthCheck, error) {
|
||||
var test []string
|
||||
var httpPath string
|
||||
var httpPort, tcpPort, timeout, interval, retries, startPeriod int32
|
||||
var disable bool
|
||||
|
||||
for key, value := range labels {
|
||||
switch key {
|
||||
case HealthCheckReadinessDisable:
|
||||
disable = cast.ToBool(value)
|
||||
case HealthCheckReadinessTest:
|
||||
if len(value) > 0 {
|
||||
test, _ = shlex.Split(value)
|
||||
}
|
||||
case HealthCheckReadinessHTTPGetPath:
|
||||
httpPath = value
|
||||
case HealthCheckReadinessHTTPGetPort:
|
||||
httpPort = cast.ToInt32(value)
|
||||
case HealthCheckReadinessTCPPort:
|
||||
tcpPort = cast.ToInt32(value)
|
||||
case HealthCheckReadinessInterval:
|
||||
parse, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check interval variable")
|
||||
}
|
||||
interval = int32(parse.Seconds())
|
||||
case HealthCheckReadinessTimeout:
|
||||
parse, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check timeout variable")
|
||||
}
|
||||
timeout = int32(parse.Seconds())
|
||||
case HealthCheckReadinessRetries:
|
||||
retries = cast.ToInt32(value)
|
||||
case HealthCheckReadinessStartPeriod:
|
||||
parse, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check startPeriod variable")
|
||||
}
|
||||
startPeriod = int32(parse.Seconds())
|
||||
}
|
||||
}
|
||||
|
||||
if len(test) > 0 {
|
||||
if test[0] == "NONE" {
|
||||
disable = true
|
||||
test = test[1:]
|
||||
}
|
||||
// Due to docker/cli adding "CMD-SHELL" to the struct, we remove the first element of composeHealthCheck.Test
|
||||
if test[0] == "CMD" || test[0] == "CMD-SHELL" {
|
||||
test = test[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return kobject.HealthCheck{
|
||||
Test: test,
|
||||
HTTPPath: httpPath,
|
||||
HTTPPort: httpPort,
|
||||
TCPPort: tcpPort,
|
||||
Timeout: timeout,
|
||||
Interval: interval,
|
||||
Retries: retries,
|
||||
StartPeriod: startPeriod,
|
||||
Disable: disable,
|
||||
}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Convert the HealthCheckConfig as designed by Docker to
|
||||
|
||||
a Kubernetes-compatible format.
|
||||
*/
|
||||
func parseHealthCheck(composeHealthCheck types.HealthCheckConfig, labels types.Labels) (kobject.HealthCheck, error) {
|
||||
var httpPort, tcpPort, timeout, interval, retries, startPeriod int32
|
||||
var test []string
|
||||
var httpPath string
|
||||
|
||||
// Here we convert the timeout from 1h30s (example) to 36030 seconds.
|
||||
if composeHealthCheck.Timeout != nil {
|
||||
parse, err := time.ParseDuration(composeHealthCheck.Timeout.String())
|
||||
if err != nil {
|
||||
return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check timeout variable")
|
||||
}
|
||||
timeout = int32(parse.Seconds())
|
||||
}
|
||||
|
||||
if composeHealthCheck.Interval != nil {
|
||||
parse, err := time.ParseDuration(composeHealthCheck.Interval.String())
|
||||
if err != nil {
|
||||
return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check interval variable")
|
||||
}
|
||||
interval = int32(parse.Seconds())
|
||||
}
|
||||
|
||||
if composeHealthCheck.Retries != nil {
|
||||
retries = int32(*composeHealthCheck.Retries)
|
||||
}
|
||||
|
||||
if composeHealthCheck.StartPeriod != nil {
|
||||
parse, err := time.ParseDuration(composeHealthCheck.StartPeriod.String())
|
||||
if err != nil {
|
||||
return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check startPeriod variable")
|
||||
}
|
||||
startPeriod = int32(parse.Seconds())
|
||||
}
|
||||
|
||||
if composeHealthCheck.Test != nil {
|
||||
test = composeHealthCheck.Test[1:]
|
||||
}
|
||||
|
||||
for key, value := range labels {
|
||||
switch key {
|
||||
case HealthCheckLivenessHTTPGetPath:
|
||||
httpPath = value
|
||||
case HealthCheckLivenessHTTPGetPort:
|
||||
httpPort = cast.ToInt32(value)
|
||||
case HealthCheckLivenessTCPPort:
|
||||
tcpPort = cast.ToInt32(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Due to docker/cli adding "CMD-SHELL" to the struct, we remove the first element of composeHealthCheck.Test
|
||||
return kobject.HealthCheck{
|
||||
Test: test,
|
||||
TCPPort: tcpPort,
|
||||
HTTPPath: httpPath,
|
||||
HTTPPort: httpPort,
|
||||
Timeout: timeout,
|
||||
Interval: interval,
|
||||
Retries: retries,
|
||||
StartPeriod: startPeriod,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func dockerComposeToKomposeMapping(composeObject *types.Project) (kobject.KomposeObject, error) {
|
||||
// Step 1. Initialize what's going to be returned
|
||||
komposeObject := kobject.KomposeObject{
|
||||
ServiceConfigs: make(map[string]kobject.ServiceConfig),
|
||||
LoadedFrom: "compose",
|
||||
Secrets: composeObject.Secrets,
|
||||
}
|
||||
|
||||
// Step 2. Parse through the object and convert it to kobject.KomposeObject!
|
||||
// Here we "clean up" the service configuration so we return something that includes
|
||||
// all relevant information as well as avoid the unsupported keys as well.
|
||||
for _, composeServiceConfig := range composeObject.Services {
|
||||
// Standard import
|
||||
// No need to modify before importation
|
||||
name := composeServiceConfig.Name
|
||||
serviceConfig := kobject.ServiceConfig{}
|
||||
serviceConfig.Name = name
|
||||
serviceConfig.Image = composeServiceConfig.Image
|
||||
serviceConfig.WorkingDir = composeServiceConfig.WorkingDir
|
||||
serviceConfig.Annotations = composeServiceConfig.Labels
|
||||
serviceConfig.CapAdd = composeServiceConfig.CapAdd
|
||||
serviceConfig.CapDrop = composeServiceConfig.CapDrop
|
||||
serviceConfig.Expose = composeServiceConfig.Expose
|
||||
serviceConfig.Privileged = composeServiceConfig.Privileged
|
||||
serviceConfig.User = composeServiceConfig.User
|
||||
serviceConfig.Stdin = composeServiceConfig.StdinOpen
|
||||
serviceConfig.Tty = composeServiceConfig.Tty
|
||||
serviceConfig.TmpFs = composeServiceConfig.Tmpfs
|
||||
serviceConfig.ContainerName = normalizeContainerNames(composeServiceConfig.ContainerName)
|
||||
serviceConfig.Command = composeServiceConfig.Entrypoint
|
||||
serviceConfig.Args = composeServiceConfig.Command
|
||||
serviceConfig.Labels = composeServiceConfig.Labels
|
||||
serviceConfig.HostName = composeServiceConfig.Hostname
|
||||
serviceConfig.DomainName = composeServiceConfig.DomainName
|
||||
serviceConfig.Secrets = composeServiceConfig.Secrets
|
||||
|
||||
if composeServiceConfig.StopGracePeriod != nil {
|
||||
serviceConfig.StopGracePeriod = composeServiceConfig.StopGracePeriod.String()
|
||||
}
|
||||
|
||||
if err := parseNetwork(&composeServiceConfig, &serviceConfig, composeObject); err != nil {
|
||||
return kobject.KomposeObject{}, err
|
||||
}
|
||||
|
||||
if err := parseResources(&composeServiceConfig, &serviceConfig); err != nil {
|
||||
return kobject.KomposeObject{}, err
|
||||
}
|
||||
|
||||
serviceConfig.Restart = composeServiceConfig.Restart
|
||||
|
||||
if composeServiceConfig.Deploy != nil {
|
||||
// Deploy keys
|
||||
// mode:
|
||||
serviceConfig.DeployMode = composeServiceConfig.Deploy.Mode
|
||||
// labels
|
||||
serviceConfig.DeployLabels = composeServiceConfig.Deploy.Labels
|
||||
|
||||
// restart-policy: deploy.restart_policy.condition will rewrite restart option
|
||||
// see: https://docs.docker.com/compose/compose-file/#restart_policy
|
||||
if composeServiceConfig.Deploy.RestartPolicy != nil {
|
||||
serviceConfig.Restart = composeServiceConfig.Deploy.RestartPolicy.Condition
|
||||
}
|
||||
|
||||
// replicas:
|
||||
if composeServiceConfig.Deploy.Replicas != nil {
|
||||
serviceConfig.Replicas = int(*composeServiceConfig.Deploy.Replicas)
|
||||
}
|
||||
|
||||
// placement:
|
||||
serviceConfig.Placement = loadPlacement(composeServiceConfig.Deploy.Placement)
|
||||
|
||||
if composeServiceConfig.Deploy.UpdateConfig != nil {
|
||||
serviceConfig.DeployUpdateConfig = *composeServiceConfig.Deploy.UpdateConfig
|
||||
}
|
||||
|
||||
if composeServiceConfig.Deploy.EndpointMode == "vip" {
|
||||
serviceConfig.ServiceType = string(api.ServiceTypeNodePort)
|
||||
}
|
||||
}
|
||||
|
||||
// HealthCheck Liveness
|
||||
if composeServiceConfig.HealthCheck != nil && !composeServiceConfig.HealthCheck.Disable {
|
||||
var err error
|
||||
serviceConfig.HealthChecks.Liveness, err = parseHealthCheck(*composeServiceConfig.HealthCheck, composeServiceConfig.Labels)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(err, "Unable to parse health check")
|
||||
}
|
||||
}
|
||||
|
||||
// HealthCheck Readiness
|
||||
var readiness, errReadiness = parseHealthCheckReadiness(composeServiceConfig.Labels)
|
||||
if !readiness.Disable {
|
||||
serviceConfig.HealthChecks.Readiness = readiness
|
||||
if errReadiness != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(errReadiness, "Unable to parse health check")
|
||||
}
|
||||
}
|
||||
|
||||
if serviceConfig.Restart == "unless-stopped" {
|
||||
log.Warnf("Restart policy 'unless-stopped' in service %s is not supported, convert it to 'always'", name)
|
||||
serviceConfig.Restart = "always"
|
||||
}
|
||||
|
||||
if composeServiceConfig.Build != nil {
|
||||
serviceConfig.Build = composeServiceConfig.Build.Context
|
||||
serviceConfig.Dockerfile = composeServiceConfig.Build.Dockerfile
|
||||
serviceConfig.BuildArgs = composeServiceConfig.Build.Args
|
||||
serviceConfig.BuildLabels = composeServiceConfig.Build.Labels
|
||||
}
|
||||
|
||||
// env
|
||||
parseEnvironment(&composeServiceConfig, &serviceConfig)
|
||||
|
||||
// Get env_file
|
||||
serviceConfig.EnvFile = composeServiceConfig.EnvFile
|
||||
|
||||
// Parse the ports
|
||||
// v3 uses a new format called "long syntax" starting in 3.2
|
||||
// https://docs.docker.com/compose/compose-file/#ports
|
||||
|
||||
// here we will translate `expose` too, they basically means the same thing in kubernetes
|
||||
serviceConfig.Port = loadPorts(composeServiceConfig.Ports, serviceConfig.Expose)
|
||||
|
||||
// Parse the volumes
|
||||
// Again, in v3, we use the "long syntax" for volumes in terms of parsing
|
||||
// https://docs.docker.com/compose/compose-file/#long-syntax-3
|
||||
serviceConfig.VolList = loadVolumes(composeServiceConfig.Volumes)
|
||||
if err := parseKomposeLabels(composeServiceConfig.Labels, &serviceConfig); err != nil {
|
||||
return kobject.KomposeObject{}, err
|
||||
}
|
||||
|
||||
// Log if the name will been changed
|
||||
if normalizeServiceNames(name) != name {
|
||||
log.Infof("Service name in docker-compose has been changed from %q to %q", name, normalizeServiceNames(name))
|
||||
}
|
||||
|
||||
serviceConfig.Configs = composeServiceConfig.Configs
|
||||
serviceConfig.ConfigsMetaData = composeObject.Configs
|
||||
|
||||
// Get GroupAdd, group should be mentioned in gid format but not the group name
|
||||
groupAdd, err := getGroupAdd(composeServiceConfig.GroupAdd)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(err, "GroupAdd should be mentioned in gid format, not a group name")
|
||||
}
|
||||
serviceConfig.GroupAdd = groupAdd
|
||||
|
||||
// Final step, add to the array!
|
||||
komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig
|
||||
}
|
||||
|
||||
handleVolume(&komposeObject, &composeObject.Volumes)
|
||||
return komposeObject, nil
|
||||
}
|
||||
|
||||
func parseNetwork(composeServiceConfig *types.ServiceConfig, serviceConfig *kobject.ServiceConfig, composeObject *types.Project) error {
|
||||
if len(composeServiceConfig.Networks) == 0 {
|
||||
if defaultNetwork, ok := composeObject.Networks["default"]; ok {
|
||||
normalizedNetworkName, err := normalizeNetworkNames(defaultNetwork.Name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to normalize network name")
|
||||
}
|
||||
serviceConfig.Network = append(serviceConfig.Network, normalizedNetworkName)
|
||||
}
|
||||
} else {
|
||||
var alias = ""
|
||||
for key := range composeServiceConfig.Networks {
|
||||
alias = key
|
||||
netName := composeObject.Networks[alias].Name
|
||||
|
||||
// if Network Name Field is empty in the docker-compose definition
|
||||
// we will use the alias name defined in service config file
|
||||
if netName == "" {
|
||||
netName = alias
|
||||
}
|
||||
|
||||
normalizedNetworkName, err := normalizeNetworkNames(netName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to normalize network name")
|
||||
}
|
||||
|
||||
serviceConfig.Network = append(serviceConfig.Network, normalizedNetworkName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseResources(composeServiceConfig *types.ServiceConfig, serviceConfig *kobject.ServiceConfig) error {
|
||||
serviceConfig.MemLimit = composeServiceConfig.MemLimit
|
||||
|
||||
if composeServiceConfig.Deploy != nil {
|
||||
// memory:
|
||||
// See: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
|
||||
// "The expression 0.1 is equivalent to the expression 100m, which can be read as “one hundred millicpu”."
|
||||
|
||||
// Since Deploy.Resources.Limits does not initialize, we must check type Resources before continuing
|
||||
if composeServiceConfig.Deploy.Resources.Limits != nil {
|
||||
serviceConfig.MemLimit = composeServiceConfig.Deploy.Resources.Limits.MemoryBytes
|
||||
|
||||
if composeServiceConfig.Deploy.Resources.Limits.NanoCPUs != "" {
|
||||
cpuLimit, err := strconv.ParseFloat(composeServiceConfig.Deploy.Resources.Limits.NanoCPUs, 64)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to convert cpu limits resources value")
|
||||
}
|
||||
serviceConfig.CPULimit = int64(cpuLimit * 1000)
|
||||
}
|
||||
}
|
||||
if composeServiceConfig.Deploy.Resources.Reservations != nil {
|
||||
serviceConfig.MemReservation = composeServiceConfig.Deploy.Resources.Reservations.MemoryBytes
|
||||
|
||||
if composeServiceConfig.Deploy.Resources.Reservations.NanoCPUs != "" {
|
||||
cpuReservation, err := strconv.ParseFloat(composeServiceConfig.Deploy.Resources.Reservations.NanoCPUs, 64)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to convert cpu limits reservation value")
|
||||
}
|
||||
serviceConfig.CPUReservation = int64(cpuReservation * 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseEnvironment(composeServiceConfig *types.ServiceConfig, serviceConfig *kobject.ServiceConfig) {
|
||||
// Gather the environment values
|
||||
// DockerCompose uses map[string]*string while we use []string
|
||||
// So let's convert that using this hack
|
||||
// Note: unset env pick up the env value on host if exist
|
||||
for name, value := range composeServiceConfig.Environment {
|
||||
var env kobject.EnvVar
|
||||
if value != nil {
|
||||
env = kobject.EnvVar{Name: name, Value: *value}
|
||||
} else {
|
||||
result, ok := os.LookupEnv(name)
|
||||
if ok {
|
||||
env = kobject.EnvVar{Name: name, Value: result}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
serviceConfig.Environment = append(serviceConfig.Environment, env)
|
||||
}
|
||||
}
|
||||
|
||||
func getVersionFromFile(file string) (string, error) {
|
||||
type ComposeVersion struct {
|
||||
Version string `json:"version"` // This affects YAML as well
|
||||
}
|
||||
var version ComposeVersion
|
||||
loadedFile, err := ReadFile(file)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
// parseKomposeLabels parse kompose labels, also do some validation
|
||||
func parseKomposeLabels(labels map[string]string, serviceConfig *kobject.ServiceConfig) error {
|
||||
// Label handler
|
||||
// Labels used to influence conversion of kompose will be handled
|
||||
// from here for docker-compose. Each loader will have such handler.
|
||||
if serviceConfig.Labels == nil {
|
||||
serviceConfig.Labels = make(map[string]string)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(loadedFile, &version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
for key, value := range labels {
|
||||
switch key {
|
||||
case LabelServiceType:
|
||||
serviceType, err := handleServiceType(value)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "handleServiceType failed")
|
||||
}
|
||||
|
||||
serviceConfig.ServiceType = serviceType
|
||||
case LabelServiceExpose:
|
||||
serviceConfig.ExposeService = strings.Trim(strings.ToLower(value), " ,")
|
||||
case LabelNodePortPort:
|
||||
serviceConfig.NodePortPort = cast.ToInt32(value)
|
||||
case LabelServiceExposeTLSSecret:
|
||||
serviceConfig.ExposeServiceTLS = value
|
||||
case LabelServiceExposeIngressClassName:
|
||||
serviceConfig.ExposeServiceIngressClassName = value
|
||||
case LabelImagePullSecret:
|
||||
serviceConfig.ImagePullSecret = value
|
||||
case LabelImagePullPolicy:
|
||||
serviceConfig.ImagePullPolicy = value
|
||||
default:
|
||||
serviceConfig.Labels[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return version.Version, nil
|
||||
if serviceConfig.ExposeService == "" && serviceConfig.ExposeServiceTLS != "" {
|
||||
return errors.New("kompose.service.expose.tls-secret was specified without kompose.service.expose")
|
||||
}
|
||||
|
||||
if serviceConfig.ExposeService == "" && serviceConfig.ExposeServiceIngressClassName != "" {
|
||||
return errors.New("kompose.service.expose.ingress-class-name was specified without kompose.service.expose")
|
||||
}
|
||||
|
||||
if serviceConfig.ServiceType != string(api.ServiceTypeNodePort) && serviceConfig.NodePortPort != 0 {
|
||||
return errors.New("kompose.service.type must be nodeport when assign node port value")
|
||||
}
|
||||
|
||||
if len(serviceConfig.Port) > 1 && serviceConfig.NodePortPort != 0 {
|
||||
return errors.New("cannot set kompose.service.nodeport.port when service has multiple ports")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleVolume(komposeObject *kobject.KomposeObject, volumes *types.Volumes) {
|
||||
for name := range komposeObject.ServiceConfigs {
|
||||
// retrieve volumes of service
|
||||
vols, err := retrieveVolume(name, *komposeObject)
|
||||
if err != nil {
|
||||
errors.Wrap(err, "could not retrieve vvolume")
|
||||
}
|
||||
for volName, vol := range vols {
|
||||
size, selector := getVolumeLabels(vol.VolumeName, volumes)
|
||||
if len(size) > 0 || len(selector) > 0 {
|
||||
// We can't assign value to struct field in map while iterating over it, so temporary variable `temp` is used here
|
||||
var temp = vols[volName]
|
||||
temp.PVCSize = size
|
||||
temp.SelectorValue = selector
|
||||
vols[volName] = temp
|
||||
}
|
||||
}
|
||||
// We can't assign value to struct field in map while iterating over it, so temporary variable `temp` is used here
|
||||
var temp = komposeObject.ServiceConfigs[name]
|
||||
temp.Volumes = vols
|
||||
komposeObject.ServiceConfigs[name] = temp
|
||||
}
|
||||
}
|
||||
|
||||
// returns all volumes associated with service, if `volumes_from` key is used, we have to retrieve volumes from the services which are mentioned there. Hence, recursive function is used here.
|
||||
func retrieveVolume(svcName string, komposeObject kobject.KomposeObject) (volume []kobject.Volumes, err error) {
|
||||
// if volumes-from key is present
|
||||
if komposeObject.ServiceConfigs[svcName].VolumesFrom != nil {
|
||||
// iterating over services from `volumes-from`
|
||||
for _, depSvc := range komposeObject.ServiceConfigs[svcName].VolumesFrom {
|
||||
// recursive call for retrieving volumes of services from `volumes-from`
|
||||
dVols, err := retrieveVolume(depSvc, komposeObject)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not retrieve the volume")
|
||||
}
|
||||
var cVols []kobject.Volumes
|
||||
cVols, err = ParseVols(komposeObject.ServiceConfigs[svcName].VolList, svcName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating current volumes")
|
||||
}
|
||||
|
||||
for _, cv := range cVols {
|
||||
// check whether volumes of current service is same or not as that of dependent volumes coming from `volumes-from`
|
||||
ok, dv := getVol(cv, dVols)
|
||||
if ok {
|
||||
// change current volumes service name to dependent service name
|
||||
if dv.VFrom == "" {
|
||||
cv.VFrom = dv.SvcName
|
||||
cv.SvcName = dv.SvcName
|
||||
} else {
|
||||
cv.VFrom = dv.VFrom
|
||||
cv.SvcName = dv.SvcName
|
||||
}
|
||||
cv.PVCName = dv.PVCName
|
||||
}
|
||||
volume = append(volume, cv)
|
||||
}
|
||||
// iterating over dependent volumes
|
||||
for _, dv := range dVols {
|
||||
// check whether dependent volume is already present or not
|
||||
if checkVolDependent(dv, volume) {
|
||||
// if found, add service name to `VFrom`
|
||||
dv.VFrom = dv.SvcName
|
||||
volume = append(volume, dv)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if `volumes-from` is not present
|
||||
volume, err = ParseVols(komposeObject.ServiceConfigs[svcName].VolList, svcName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating current volumes")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// checkVolDependent returns false if dependent volume is present
|
||||
func checkVolDependent(dv kobject.Volumes, volume []kobject.Volumes) bool {
|
||||
for _, vol := range volume {
|
||||
if vol.PVCName == dv.PVCName {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ParseVols parse volumes
|
||||
func ParseVols(volNames []string, svcName string) ([]kobject.Volumes, error) {
|
||||
var volumes []kobject.Volumes
|
||||
var err error
|
||||
|
||||
for i, vn := range volNames {
|
||||
var v kobject.Volumes
|
||||
v.VolumeName, v.Host, v.Container, v.Mode, err = transformer.ParseVolume(vn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse volume %q: %v", vn, err)
|
||||
}
|
||||
v.VolumeName = normalizeVolumes(v.VolumeName)
|
||||
v.SvcName = svcName
|
||||
v.MountPath = fmt.Sprintf("%s:%s", v.Host, v.Container)
|
||||
v.PVCName = fmt.Sprintf("%s-claim%d", v.SvcName, i)
|
||||
volumes = append(volumes, v)
|
||||
}
|
||||
|
||||
return volumes, nil
|
||||
}
|
||||
|
||||
// for dependent volumes, returns true and the respective volume if mountpath are same
|
||||
func getVol(toFind kobject.Volumes, Vols []kobject.Volumes) (bool, kobject.Volumes) {
|
||||
for _, dv := range Vols {
|
||||
if toFind.MountPath == dv.MountPath {
|
||||
return true, dv
|
||||
}
|
||||
}
|
||||
return false, kobject.Volumes{}
|
||||
}
|
||||
|
||||
func getVolumeLabels(name string, volumes *types.Volumes) (string, string) {
|
||||
size, selector := "", ""
|
||||
|
||||
if volume, ok := (*volumes)[name]; ok {
|
||||
for key, value := range volume.Labels {
|
||||
if key == "kompose.volume.size" {
|
||||
size = value
|
||||
} else if key == "kompose.volume.selector" {
|
||||
selector = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size, selector
|
||||
}
|
||||
|
||||
// getGroupAdd will return group in int64 format
|
||||
func getGroupAdd(group []string) ([]int64, error) {
|
||||
var groupAdd []int64
|
||||
for _, i := range group {
|
||||
j, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to get group_add key")
|
||||
}
|
||||
groupAdd = append(groupAdd, int64(j))
|
||||
}
|
||||
return groupAdd, nil
|
||||
}
|
||||
|
||||
@@ -24,10 +24,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/compose/types"
|
||||
"github.com/docker/libcompose/config"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/docker/libcompose/yaml"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/kubernetes/kompose/pkg/kobject"
|
||||
"github.com/pkg/errors"
|
||||
@@ -203,7 +200,7 @@ func TestLoadV3Volumes(t *testing.T) {
|
||||
ReadOnly: true,
|
||||
}
|
||||
volumes := []types.ServiceVolumeConfig{vol}
|
||||
output := loadV3Volumes(volumes)
|
||||
output := loadVolumes(volumes)
|
||||
expected := "/tmp/foobar:/tmp/foobar:ro"
|
||||
|
||||
if output[0] != expected {
|
||||
@@ -220,7 +217,7 @@ func TestLoadV3Ports(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
desc: "ports with expose",
|
||||
ports: []types.ServicePortConfig{{Target: 80, Published: 80, Protocol: string(api.ProtocolTCP)}},
|
||||
ports: []types.ServicePortConfig{{Target: 80, Published: "80", Protocol: string(api.ProtocolTCP)}},
|
||||
expose: []string{"80", "8080"},
|
||||
want: []kobject.Ports{
|
||||
{HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)},
|
||||
@@ -229,7 +226,7 @@ func TestLoadV3Ports(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "exposed port including /protocol",
|
||||
ports: []types.ServicePortConfig{{Target: 80, Published: 80, Protocol: string(api.ProtocolTCP)}},
|
||||
ports: []types.ServicePortConfig{{Target: 80, Published: "80", Protocol: string(api.ProtocolTCP)}},
|
||||
expose: []string{"80/udp"},
|
||||
want: []kobject.Ports{
|
||||
{HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)},
|
||||
@@ -238,7 +235,7 @@ func TestLoadV3Ports(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
got := loadV3Ports(tt.ports, tt.expose)
|
||||
got := loadPorts(tt.ports, tt.expose)
|
||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
t.Errorf("loadV3Ports() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
@@ -275,92 +272,90 @@ func TestHandleServiceType(t *testing.T) {
|
||||
|
||||
// Test loading of ports
|
||||
func TestLoadPorts(t *testing.T) {
|
||||
portWithIPAddress, _ := types.ParsePortConfig("127.0.0.1:80:80/tcp")
|
||||
portWithoutIPAddress, _ := types.ParsePortConfig("80:80/tcp")
|
||||
portWithoutProtocol, _ := types.ParsePortConfig("80:80")
|
||||
singlePort, _ := types.ParsePortConfig("80")
|
||||
singlePortsRange, _ := types.ParsePortConfig("3000-3002")
|
||||
targetAndContainerPortsRange, _ := types.ParsePortConfig("3000-3002:5000-5002")
|
||||
targetAndContainerPortsRangeWithIPAddress, _ := types.ParsePortConfig("127.0.0.1:3000-3002:5000-5002")
|
||||
port3000, _ := types.ParsePortConfig("3000")
|
||||
|
||||
tests := []struct {
|
||||
ports []string
|
||||
ports []types.ServicePortConfig
|
||||
expose []string
|
||||
want []kobject.Ports
|
||||
}{
|
||||
{
|
||||
ports: []string{"127.0.0.1:80:80/tcp"},
|
||||
ports: portWithIPAddress,
|
||||
want: []kobject.Ports{
|
||||
{HostIP: "127.0.0.1", HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)},
|
||||
},
|
||||
},
|
||||
{
|
||||
ports: []string{"80:80/tcp"},
|
||||
ports: portWithoutIPAddress,
|
||||
want: []kobject.Ports{
|
||||
{HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)},
|
||||
},
|
||||
},
|
||||
{
|
||||
ports: []string{"80:80"},
|
||||
ports: portWithoutProtocol,
|
||||
want: []kobject.Ports{
|
||||
{HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)},
|
||||
},
|
||||
},
|
||||
{
|
||||
ports: []string{"80"},
|
||||
ports: singlePort,
|
||||
want: []kobject.Ports{
|
||||
{ContainerPort: 80, Protocol: string(api.ProtocolTCP)},
|
||||
},
|
||||
},
|
||||
{
|
||||
ports: []string{"3000-3005"},
|
||||
ports: singlePortsRange,
|
||||
want: []kobject.Ports{
|
||||
{ContainerPort: 3000, Protocol: string(api.ProtocolTCP)},
|
||||
{ContainerPort: 3001, Protocol: string(api.ProtocolTCP)},
|
||||
{ContainerPort: 3002, Protocol: string(api.ProtocolTCP)},
|
||||
{ContainerPort: 3003, Protocol: string(api.ProtocolTCP)},
|
||||
{ContainerPort: 3004, Protocol: string(api.ProtocolTCP)},
|
||||
{ContainerPort: 3005, Protocol: string(api.ProtocolTCP)},
|
||||
},
|
||||
},
|
||||
{
|
||||
ports: []string{"3000-3005:5000-5005"},
|
||||
ports: targetAndContainerPortsRange,
|
||||
want: []kobject.Ports{
|
||||
{HostPort: 3000, ContainerPort: 5000, Protocol: string(api.ProtocolTCP)},
|
||||
{HostPort: 3001, ContainerPort: 5001, Protocol: string(api.ProtocolTCP)},
|
||||
{HostPort: 3002, ContainerPort: 5002, Protocol: string(api.ProtocolTCP)},
|
||||
{HostPort: 3003, ContainerPort: 5003, Protocol: string(api.ProtocolTCP)},
|
||||
{HostPort: 3004, ContainerPort: 5004, Protocol: string(api.ProtocolTCP)},
|
||||
{HostPort: 3005, ContainerPort: 5005, Protocol: string(api.ProtocolTCP)},
|
||||
},
|
||||
},
|
||||
{
|
||||
ports: []string{"127.0.0.1:3000-3005:5000-5005"},
|
||||
ports: targetAndContainerPortsRangeWithIPAddress,
|
||||
want: []kobject.Ports{
|
||||
{HostIP: "127.0.0.1", HostPort: 3000, ContainerPort: 5000, Protocol: string(api.ProtocolTCP)},
|
||||
{HostIP: "127.0.0.1", HostPort: 3001, ContainerPort: 5001, Protocol: string(api.ProtocolTCP)},
|
||||
{HostIP: "127.0.0.1", HostPort: 3002, ContainerPort: 5002, Protocol: string(api.ProtocolTCP)},
|
||||
{HostIP: "127.0.0.1", HostPort: 3003, ContainerPort: 5003, Protocol: string(api.ProtocolTCP)},
|
||||
{HostIP: "127.0.0.1", HostPort: 3004, ContainerPort: 5004, Protocol: string(api.ProtocolTCP)},
|
||||
{HostIP: "127.0.0.1", HostPort: 3005, ContainerPort: 5005, Protocol: string(api.ProtocolTCP)},
|
||||
},
|
||||
},
|
||||
{
|
||||
ports: []string{"80", "3000"},
|
||||
ports: append(append([]types.ServicePortConfig{}, singlePort...), port3000...),
|
||||
want: []kobject.Ports{
|
||||
{HostPort: 0, ContainerPort: 80, Protocol: string(api.ProtocolTCP)},
|
||||
{HostPort: 0, ContainerPort: 3000, Protocol: string(api.ProtocolTCP)},
|
||||
},
|
||||
},
|
||||
{
|
||||
ports: []string{"80", "3000"},
|
||||
ports: append(append([]types.ServicePortConfig{}, singlePort...), port3000...),
|
||||
expose: []string{"80", "8080"},
|
||||
want: []kobject.Ports{
|
||||
{HostPort: 0, ContainerPort: 80, Protocol: string(api.ProtocolTCP)},
|
||||
{HostPort: 0, ContainerPort: 3000, Protocol: string(api.ProtocolTCP)},
|
||||
{HostPort: 0, ContainerPort: 8080, Protocol: string(api.ProtocolTCP)},
|
||||
{ContainerPort: 80, Protocol: string(api.ProtocolTCP)},
|
||||
{ContainerPort: 3000, Protocol: string(api.ProtocolTCP)},
|
||||
{HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)},
|
||||
{HostPort: 8080, ContainerPort: 8080, Protocol: string(api.ProtocolTCP)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("port=%q,expose=%q", tt.ports, tt.expose), func(t *testing.T) {
|
||||
got, err := loadPorts(tt.ports, tt.expose)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error with loading ports %v", err)
|
||||
}
|
||||
got := loadPorts(tt.ports, tt.expose)
|
||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
t.Errorf("loadPorts() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
@@ -438,71 +433,60 @@ func TestLoadEnvVar(t *testing.T) {
|
||||
// docker-compose projects
|
||||
func TestUnsupportedKeys(t *testing.T) {
|
||||
// create project that will be used in test cases
|
||||
projectWithNetworks := project.NewProject(&project.Context{}, nil, nil)
|
||||
projectWithNetworks.ServiceConfigs = config.NewServiceConfigs()
|
||||
projectWithNetworks.ServiceConfigs.Add("foo", &config.ServiceConfig{
|
||||
Image: "foo/bar",
|
||||
Build: yaml.Build{
|
||||
Context: "./build",
|
||||
projectWithNetworks := &types.Project{
|
||||
Networks: types.Networks{
|
||||
"foo": types.NetworkConfig{
|
||||
Name: "foo",
|
||||
Driver: "bridge",
|
||||
},
|
||||
},
|
||||
Hostname: "localhost",
|
||||
Ports: []string{}, // test empty array
|
||||
Networks: &yaml.Networks{
|
||||
Networks: []*yaml.Network{
|
||||
{
|
||||
Name: "net1",
|
||||
Services: types.Services{
|
||||
types.ServiceConfig{
|
||||
Name: "foo",
|
||||
Image: "foo/bar",
|
||||
Build: &types.BuildConfig{
|
||||
Context: "./build",
|
||||
},
|
||||
Hostname: "localhost",
|
||||
Ports: []types.ServicePortConfig{}, // test empty array
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"net1": {},
|
||||
},
|
||||
},
|
||||
types.ServiceConfig{
|
||||
Name: "bar",
|
||||
Image: "bar/foo",
|
||||
Build: &types.BuildConfig{
|
||||
Context: "./build",
|
||||
},
|
||||
Hostname: "localhost",
|
||||
Ports: []types.ServicePortConfig{}, // test empty array
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"net1": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
projectWithNetworks.ServiceConfigs.Add("bar", &config.ServiceConfig{
|
||||
Image: "bar/foo",
|
||||
Build: yaml.Build{
|
||||
Context: "./build",
|
||||
},
|
||||
Hostname: "localhost",
|
||||
Ports: []string{}, // test empty array
|
||||
Networks: &yaml.Networks{
|
||||
Networks: []*yaml.Network{
|
||||
{
|
||||
Name: "net1",
|
||||
},
|
||||
Volumes: types.Volumes{
|
||||
"foo": types.VolumeConfig{
|
||||
Name: "foo",
|
||||
Driver: "storage",
|
||||
},
|
||||
},
|
||||
})
|
||||
projectWithNetworks.VolumeConfigs = map[string]*config.VolumeConfig{
|
||||
"foo": {
|
||||
Driver: "storage",
|
||||
},
|
||||
}
|
||||
projectWithNetworks.NetworkConfigs = map[string]*config.NetworkConfig{
|
||||
"foo": {
|
||||
Driver: "bridge",
|
||||
},
|
||||
}
|
||||
|
||||
projectWithEmptyNetwork := project.NewProject(&project.Context{}, nil, nil)
|
||||
projectWithEmptyNetwork.ServiceConfigs = config.NewServiceConfigs()
|
||||
projectWithEmptyNetwork.ServiceConfigs.Add("foo", &config.ServiceConfig{
|
||||
Networks: &yaml.Networks{},
|
||||
})
|
||||
|
||||
projectWithDefaultNetwork := project.NewProject(&project.Context{}, nil, nil)
|
||||
projectWithDefaultNetwork.ServiceConfigs = config.NewServiceConfigs()
|
||||
|
||||
projectWithDefaultNetwork.ServiceConfigs.Add("foo", &config.ServiceConfig{
|
||||
Networks: &yaml.Networks{
|
||||
Networks: []*yaml.Network{
|
||||
{
|
||||
Name: "default",
|
||||
projectWithDefaultNetwork := &types.Project{
|
||||
Services: types.Services{
|
||||
types.ServiceConfig{
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// define all test cases for checkUnsupportedKey function
|
||||
testCases := map[string]struct {
|
||||
composeProject *project.Project
|
||||
composeProject *types.Project
|
||||
expectedUnsupportedKeys []string
|
||||
}{
|
||||
"With Networks (service and root level)": {
|
||||
@@ -567,28 +551,6 @@ func TestNormalizeNetworkNames(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLabelsPorts(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
noOfPort int
|
||||
labels string
|
||||
svcName string
|
||||
expectError bool
|
||||
}{
|
||||
{"ports is defined", 1, "NodePort", "foo", false},
|
||||
{"ports is not defined", 0, "NodePort", "foo", true},
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, testcase := range testCases {
|
||||
t.Log(testcase.name)
|
||||
err = checkLabelsPorts(testcase.noOfPort, testcase.labels, testcase.svcName)
|
||||
if testcase.expectError && err == nil {
|
||||
t.Log("Expected error, got ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckPlacementCustomLabels(t *testing.T) {
|
||||
placement := types.Placement{
|
||||
Constraints: []string{
|
||||
@@ -601,7 +563,7 @@ func TestCheckPlacementCustomLabels(t *testing.T) {
|
||||
{Spread: "node.labels.ssd"},
|
||||
},
|
||||
}
|
||||
output := loadV3Placement(placement)
|
||||
output := loadPlacement(placement)
|
||||
|
||||
expected := kobject.Placement{
|
||||
PositiveConstraints: map[string]string{
|
||||
|
||||
@@ -1,417 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/docker/libcompose/config"
|
||||
"github.com/docker/libcompose/lookup"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/kubernetes/kompose/pkg/kobject"
|
||||
"github.com/kubernetes/kompose/pkg/transformer"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cast"
|
||||
api "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Parse Docker Compose with libcompose (only supports v1 and v2). Eventually we will
|
||||
// switch to using only libcompose once v3 is supported.
|
||||
func parseV1V2(files []string) (kobject.KomposeObject, error) {
|
||||
// Gather the appropriate context for parsing
|
||||
context := &project.Context{}
|
||||
context.ComposeFiles = files
|
||||
|
||||
if context.ResourceLookup == nil {
|
||||
context.ResourceLookup = &lookup.FileResourceLookup{}
|
||||
}
|
||||
|
||||
if context.EnvironmentLookup == nil {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, nil
|
||||
}
|
||||
context.EnvironmentLookup = &lookup.ComposableEnvLookup{
|
||||
Lookups: []config.EnvironmentLookup{
|
||||
&lookup.EnvfileLookup{
|
||||
Path: filepath.Join(cwd, ".env"),
|
||||
},
|
||||
&lookup.OsEnvLookup{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Load the context and let's start parsing
|
||||
composeObject := project.NewProject(context, nil, nil)
|
||||
err := composeObject.Parse()
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(err, "composeObject.Parse() failed, Failed to load compose file")
|
||||
}
|
||||
|
||||
noSupKeys := checkUnsupportedKey(composeObject)
|
||||
for _, keyName := range noSupKeys {
|
||||
log.Warningf("Unsupported %s key - ignoring", keyName)
|
||||
}
|
||||
|
||||
// Map the parsed struct to a struct we understand (kobject)
|
||||
komposeObject, err := libComposeToKomposeMapping(composeObject)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, err
|
||||
}
|
||||
|
||||
return komposeObject, nil
|
||||
}
|
||||
|
||||
// Load ports from compose file
|
||||
// also load `expose` here
|
||||
func loadPorts(composePorts []string, expose []string) ([]kobject.Ports, error) {
|
||||
kp := []kobject.Ports{}
|
||||
exist := map[string]bool{}
|
||||
for _, cp := range composePorts {
|
||||
var hostIP string
|
||||
|
||||
if parts := strings.Split(cp, ":"); len(parts) == 3 {
|
||||
if ip := net.ParseIP(parts[0]); ip.To4() == nil && ip.To16() == nil {
|
||||
return nil, fmt.Errorf("%q contains an invalid IPv4 or IPv6 IP address", parts[0])
|
||||
}
|
||||
hostIP = parts[0]
|
||||
}
|
||||
|
||||
np, pbs, err := nat.ParsePortSpecs([]string{cp})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid port, error = %v", err)
|
||||
}
|
||||
// Force HostIP value to avoid warning raised by github.com/docker/cli/opts
|
||||
// The opts package will warn if the bindings contains host IP except
|
||||
// 0.0.0.0. However, the message is not useful in this case since the value
|
||||
// should be handled by kompose properly.
|
||||
for _, pb := range pbs {
|
||||
for i, p := range pb {
|
||||
p.HostIP = ""
|
||||
pb[i] = p
|
||||
}
|
||||
}
|
||||
|
||||
var ports []string
|
||||
for p := range np {
|
||||
ports = append(ports, string(p))
|
||||
}
|
||||
sort.Strings(ports)
|
||||
|
||||
for _, p := range ports {
|
||||
pc, err := opts.ConvertPortToPortConfig(nat.Port(p), pbs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid port, error = %v", err)
|
||||
}
|
||||
for _, cfg := range pc {
|
||||
kp = append(kp, kobject.Ports{
|
||||
HostPort: int32(cfg.PublishedPort),
|
||||
ContainerPort: int32(cfg.TargetPort),
|
||||
HostIP: hostIP,
|
||||
Protocol: strings.ToUpper(string(cfg.Protocol)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load remain expose ports
|
||||
for _, p := range kp {
|
||||
// must use cast...
|
||||
exist[cast.ToString(p.ContainerPort)+p.Protocol] = true
|
||||
}
|
||||
|
||||
if expose != nil {
|
||||
for _, port := range expose {
|
||||
portValue := port
|
||||
protocol := string(api.ProtocolTCP)
|
||||
if strings.Contains(portValue, "/") {
|
||||
splits := strings.Split(port, "/")
|
||||
portValue = splits[0]
|
||||
protocol = splits[1]
|
||||
}
|
||||
|
||||
if !exist[portValue+protocol] {
|
||||
kp = append(kp, kobject.Ports{
|
||||
ContainerPort: cast.ToInt32(portValue),
|
||||
Protocol: strings.ToUpper(protocol),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return kp, nil
|
||||
}
|
||||
|
||||
// Uses libcompose's APIProject type and converts it to a Kompose object for us to understand
|
||||
func libComposeToKomposeMapping(composeObject *project.Project) (kobject.KomposeObject, error) {
|
||||
// Initialize what's going to be returned
|
||||
komposeObject := kobject.KomposeObject{
|
||||
ServiceConfigs: make(map[string]kobject.ServiceConfig),
|
||||
LoadedFrom: "compose",
|
||||
}
|
||||
|
||||
// Here we "clean up" the service configuration so we return something that includes
|
||||
// all relevant information as well as avoid the unsupported keys as well.
|
||||
for name, composeServiceConfig := range composeObject.ServiceConfigs.All() {
|
||||
serviceConfig := kobject.ServiceConfig{}
|
||||
serviceConfig.Name = name
|
||||
serviceConfig.Image = composeServiceConfig.Image
|
||||
serviceConfig.Build = composeServiceConfig.Build.Context
|
||||
newName := normalizeContainerNames(composeServiceConfig.ContainerName)
|
||||
serviceConfig.ContainerName = newName
|
||||
if newName != composeServiceConfig.ContainerName {
|
||||
log.Infof("Container name in service %q has been changed from %q to %q", name, composeServiceConfig.ContainerName, newName)
|
||||
}
|
||||
serviceConfig.Command = composeServiceConfig.Entrypoint
|
||||
serviceConfig.HostName = composeServiceConfig.Hostname
|
||||
serviceConfig.DomainName = composeServiceConfig.DomainName
|
||||
serviceConfig.Args = composeServiceConfig.Command
|
||||
serviceConfig.Dockerfile = composeServiceConfig.Build.Dockerfile
|
||||
serviceConfig.BuildArgs = composeServiceConfig.Build.Args
|
||||
serviceConfig.Expose = composeServiceConfig.Expose
|
||||
|
||||
envs := loadEnvVars(composeServiceConfig.Environment)
|
||||
serviceConfig.Environment = envs
|
||||
|
||||
// Validate dockerfile path
|
||||
if filepath.IsAbs(serviceConfig.Dockerfile) {
|
||||
log.Fatalf("%q defined in service %q is an absolute path, it must be a relative path.", serviceConfig.Dockerfile, name)
|
||||
}
|
||||
|
||||
// load ports, same as v3, we also load `expose`
|
||||
ports, err := loadPorts(composeServiceConfig.Ports, serviceConfig.Expose)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(err, "loadPorts failed. "+name+" failed to load ports from compose file")
|
||||
}
|
||||
serviceConfig.Port = ports
|
||||
|
||||
serviceConfig.WorkingDir = composeServiceConfig.WorkingDir
|
||||
|
||||
if composeServiceConfig.Volumes != nil {
|
||||
for _, volume := range composeServiceConfig.Volumes.Volumes {
|
||||
v := volume.String()
|
||||
serviceConfig.VolList = append(serviceConfig.VolList, v)
|
||||
}
|
||||
}
|
||||
|
||||
// canonical "Custom Labels" handler
|
||||
// Labels used to influence conversion of kompose will be handled
|
||||
// from here for docker-compose. Each loader will have such handler.
|
||||
if err := parseKomposeLabels(composeServiceConfig.Labels, &serviceConfig); err != nil {
|
||||
return kobject.KomposeObject{}, err
|
||||
}
|
||||
|
||||
err = checkLabelsPorts(len(serviceConfig.Port), composeServiceConfig.Labels[LabelServiceType], name)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(err, "kompose.service.type can't be set if service doesn't expose any ports.")
|
||||
}
|
||||
|
||||
// convert compose labels to annotations
|
||||
serviceConfig.Annotations = composeServiceConfig.Labels
|
||||
serviceConfig.CPUQuota = int64(composeServiceConfig.CPUQuota)
|
||||
serviceConfig.CapAdd = composeServiceConfig.CapAdd
|
||||
serviceConfig.CapDrop = composeServiceConfig.CapDrop
|
||||
serviceConfig.Pid = composeServiceConfig.Pid
|
||||
|
||||
serviceConfig.Privileged = composeServiceConfig.Privileged
|
||||
serviceConfig.User = composeServiceConfig.User
|
||||
serviceConfig.VolumesFrom = composeServiceConfig.VolumesFrom
|
||||
serviceConfig.Stdin = composeServiceConfig.StdinOpen
|
||||
serviceConfig.Tty = composeServiceConfig.Tty
|
||||
serviceConfig.MemLimit = composeServiceConfig.MemLimit
|
||||
serviceConfig.TmpFs = composeServiceConfig.Tmpfs
|
||||
serviceConfig.StopGracePeriod = composeServiceConfig.StopGracePeriod
|
||||
|
||||
// pretty much same as v3
|
||||
serviceConfig.Restart = composeServiceConfig.Restart
|
||||
if serviceConfig.Restart == "unless-stopped" {
|
||||
log.Warnf("Restart policy 'unless-stopped' in service %s is not supported, convert it to 'always'", name)
|
||||
serviceConfig.Restart = "always"
|
||||
}
|
||||
|
||||
if composeServiceConfig.Networks != nil {
|
||||
if len(composeServiceConfig.Networks.Networks) > 0 {
|
||||
for _, value := range composeServiceConfig.Networks.Networks {
|
||||
if value.Name != "default" {
|
||||
nomalizedNetworkName, err := normalizeNetworkNames(value.RealName)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(err, "Error trying to normalize network names")
|
||||
}
|
||||
if nomalizedNetworkName != value.RealName {
|
||||
log.Warnf("Network name in docker-compose has been changed from %q to %q", value.RealName, nomalizedNetworkName)
|
||||
}
|
||||
serviceConfig.Network = append(serviceConfig.Network, nomalizedNetworkName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get GroupAdd, group should be mentioned in gid format but not the group name
|
||||
groupAdd, err := getGroupAdd(composeServiceConfig.GroupAdd)
|
||||
if err != nil {
|
||||
return kobject.KomposeObject{}, errors.Wrap(err, "GroupAdd should be mentioned in gid format, not a group name")
|
||||
}
|
||||
serviceConfig.GroupAdd = groupAdd
|
||||
|
||||
komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig
|
||||
if normalizeServiceNames(name) != name {
|
||||
log.Infof("Service name in docker-compose has been changed from %q to %q", name, normalizeServiceNames(name))
|
||||
}
|
||||
}
|
||||
|
||||
// This will handle volume at earlier stage itself, it will resolves problems occurred due to `volumes_from` key
|
||||
handleVolume(&komposeObject)
|
||||
|
||||
return komposeObject, nil
|
||||
}
|
||||
|
||||
// This function will retrieve volumes for each service, as well as it will parse volume information and store it in Volumes struct
|
||||
func handleVolume(komposeObject *kobject.KomposeObject) {
|
||||
for name := range komposeObject.ServiceConfigs {
|
||||
// retrieve volumes of service
|
||||
vols, err := retrieveVolume(name, *komposeObject)
|
||||
if err != nil {
|
||||
errors.Wrap(err, "could not retrieve volume")
|
||||
}
|
||||
// We can't assign value to struct field in map while iterating over it, so temporary variable `temp` is used here
|
||||
var temp = komposeObject.ServiceConfigs[name]
|
||||
temp.Volumes = vols
|
||||
komposeObject.ServiceConfigs[name] = temp
|
||||
}
|
||||
}
|
||||
|
||||
func checkLabelsPorts(noOfPort int, labels string, svcName string) error {
|
||||
if noOfPort == 0 && (labels == "NodePort" || labels == "LoadBalancer") {
|
||||
return errors.Errorf("%s defined in service %s with no ports present. Issues may occur when bringing up artifacts.", labels, svcName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns all volumes associated with service, if `volumes_from` key is used, we have to retrieve volumes from the services which are mentioned there. Hence, recursive function is used here.
|
||||
func retrieveVolume(svcName string, komposeObject kobject.KomposeObject) (volume []kobject.Volumes, err error) {
|
||||
// if volumes-from key is present
|
||||
if komposeObject.ServiceConfigs[svcName].VolumesFrom != nil {
|
||||
// iterating over services from `volumes-from`
|
||||
for _, depSvc := range komposeObject.ServiceConfigs[svcName].VolumesFrom {
|
||||
// recursive call for retrieving volumes of services from `volumes-from`
|
||||
dVols, err := retrieveVolume(depSvc, komposeObject)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not retrieve the volume")
|
||||
}
|
||||
var cVols []kobject.Volumes
|
||||
cVols, err = ParseVols(komposeObject.ServiceConfigs[svcName].VolList, svcName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating current volumes")
|
||||
}
|
||||
|
||||
for _, cv := range cVols {
|
||||
// check whether volumes of current service is same or not as that of dependent volumes coming from `volumes-from`
|
||||
ok, dv := getVol(cv, dVols)
|
||||
if ok {
|
||||
// change current volumes service name to dependent service name
|
||||
if dv.VFrom == "" {
|
||||
cv.VFrom = dv.SvcName
|
||||
cv.SvcName = dv.SvcName
|
||||
} else {
|
||||
cv.VFrom = dv.VFrom
|
||||
cv.SvcName = dv.SvcName
|
||||
}
|
||||
cv.PVCName = dv.PVCName
|
||||
}
|
||||
volume = append(volume, cv)
|
||||
}
|
||||
// iterating over dependent volumes
|
||||
for _, dv := range dVols {
|
||||
// check whether dependent volume is already present or not
|
||||
if checkVolDependent(dv, volume) {
|
||||
// if found, add service name to `VFrom`
|
||||
dv.VFrom = dv.SvcName
|
||||
volume = append(volume, dv)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if `volumes-from` is not present
|
||||
volume, err = ParseVols(komposeObject.ServiceConfigs[svcName].VolList, svcName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating current volumes")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// checkVolDependent returns false if dependent volume is present
|
||||
func checkVolDependent(dv kobject.Volumes, volume []kobject.Volumes) bool {
|
||||
for _, vol := range volume {
|
||||
if vol.PVCName == dv.PVCName {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ParseVols parse volumes
|
||||
func ParseVols(volNames []string, svcName string) ([]kobject.Volumes, error) {
|
||||
var volumes []kobject.Volumes
|
||||
var err error
|
||||
|
||||
for i, vn := range volNames {
|
||||
var v kobject.Volumes
|
||||
v.VolumeName, v.Host, v.Container, v.Mode, err = transformer.ParseVolume(vn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse volume %q: %v", vn, err)
|
||||
}
|
||||
v.VolumeName = normalizeVolumes(v.VolumeName)
|
||||
v.SvcName = svcName
|
||||
v.MountPath = fmt.Sprintf("%s:%s", v.Host, v.Container)
|
||||
v.PVCName = fmt.Sprintf("%s-claim%d", v.SvcName, i)
|
||||
volumes = append(volumes, v)
|
||||
}
|
||||
|
||||
return volumes, nil
|
||||
}
|
||||
|
||||
// for dependent volumes, returns true and the respective volume if mountpath are same
|
||||
func getVol(toFind kobject.Volumes, Vols []kobject.Volumes) (bool, kobject.Volumes) {
|
||||
for _, dv := range Vols {
|
||||
if toFind.MountPath == dv.MountPath {
|
||||
return true, dv
|
||||
}
|
||||
}
|
||||
return false, kobject.Volumes{}
|
||||
}
|
||||
|
||||
// getGroupAdd will return group in int64 format
|
||||
func getGroupAdd(group []string) ([]int64, error) {
|
||||
var groupAdd []int64
|
||||
for _, i := range group {
|
||||
j, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to get group_add key")
|
||||
}
|
||||
groupAdd = append(groupAdd, int64(j))
|
||||
}
|
||||
return groupAdd, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user