github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/stack/kubernetes/convert.go (about)

     1  package kubernetes
     2  
     3  import (
     4  	"io"
     5  	"io/ioutil"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/docker/cli/cli/compose/loader"
    11  	"github.com/docker/cli/cli/compose/schema"
    12  	composeTypes "github.com/docker/cli/cli/compose/types"
    13  	composetypes "github.com/docker/cli/cli/compose/types"
    14  	"github.com/docker/cli/kubernetes/compose/v1beta1"
    15  	"github.com/docker/cli/kubernetes/compose/v1beta2"
    16  	"github.com/pkg/errors"
    17  	yaml "gopkg.in/yaml.v2"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  )
    20  
    21  // NewStackConverter returns a converter from types.Config (compose) to the specified
    22  // stack version or error out if the version is not supported or existent.
    23  func NewStackConverter(version string) (StackConverter, error) {
    24  	switch version {
    25  	case "v1beta1":
    26  		return stackV1Beta1Converter{}, nil
    27  	case "v1beta2":
    28  		return stackV1Beta2Converter{}, nil
    29  	default:
    30  		return nil, errors.Errorf("stack version %s unsupported", version)
    31  	}
    32  }
    33  
    34  // StackConverter converts a compose types.Config to a Stack
    35  type StackConverter interface {
    36  	FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error)
    37  }
    38  
    39  type stackV1Beta1Converter struct{}
    40  
    41  func (s stackV1Beta1Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
    42  	cfg.Version = v1beta1.MaxComposeVersion
    43  	st, err := fromCompose(stderr, name, cfg)
    44  	if err != nil {
    45  		return Stack{}, err
    46  	}
    47  	res, err := yaml.Marshal(cfg)
    48  	if err != nil {
    49  		return Stack{}, err
    50  	}
    51  	// reload the result to check that it produced a valid 3.5 compose file
    52  	resparsedConfig, err := loader.ParseYAML(res)
    53  	if err != nil {
    54  		return Stack{}, err
    55  	}
    56  	if err = schema.Validate(resparsedConfig, v1beta1.MaxComposeVersion); err != nil {
    57  		return Stack{}, errors.Wrapf(err, "the compose yaml file is invalid with v%s", v1beta1.MaxComposeVersion)
    58  	}
    59  
    60  	st.ComposeFile = string(res)
    61  	return st, nil
    62  }
    63  
    64  type stackV1Beta2Converter struct{}
    65  
    66  func (s stackV1Beta2Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
    67  	return fromCompose(stderr, name, cfg)
    68  }
    69  
    70  func fromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
    71  	return Stack{
    72  		Name: name,
    73  		Spec: fromComposeConfig(stderr, cfg),
    74  	}, nil
    75  }
    76  
    77  func loadStackData(composefile string) (*composetypes.Config, error) {
    78  	parsed, err := loader.ParseYAML([]byte(composefile))
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	return loader.Load(composetypes.ConfigDetails{
    83  		ConfigFiles: []composetypes.ConfigFile{
    84  			{
    85  				Config: parsed,
    86  			},
    87  		},
    88  	})
    89  }
    90  
    91  // Conversions from internal stack to different stack compose component versions.
    92  func stackFromV1beta1(in *v1beta1.Stack) (Stack, error) {
    93  	cfg, err := loadStackData(in.Spec.ComposeFile)
    94  	if err != nil {
    95  		return Stack{}, err
    96  	}
    97  	return Stack{
    98  		Name:        in.ObjectMeta.Name,
    99  		Namespace:   in.ObjectMeta.Namespace,
   100  		ComposeFile: in.Spec.ComposeFile,
   101  		Spec:        fromComposeConfig(ioutil.Discard, cfg),
   102  	}, nil
   103  }
   104  
   105  func stackToV1beta1(s Stack) *v1beta1.Stack {
   106  	return &v1beta1.Stack{
   107  		ObjectMeta: metav1.ObjectMeta{
   108  			Name: s.Name,
   109  		},
   110  		Spec: v1beta1.StackSpec{
   111  			ComposeFile: s.ComposeFile,
   112  		},
   113  	}
   114  }
   115  
   116  func stackFromV1beta2(in *v1beta2.Stack) Stack {
   117  	return Stack{
   118  		Name:      in.ObjectMeta.Name,
   119  		Namespace: in.ObjectMeta.Namespace,
   120  		Spec:      in.Spec,
   121  	}
   122  }
   123  
   124  func stackToV1beta2(s Stack) *v1beta2.Stack {
   125  	return &v1beta2.Stack{
   126  		ObjectMeta: metav1.ObjectMeta{
   127  			Name: s.Name,
   128  		},
   129  		Spec: s.Spec,
   130  	}
   131  }
   132  
   133  func fromComposeConfig(stderr io.Writer, c *composeTypes.Config) *v1beta2.StackSpec {
   134  	if c == nil {
   135  		return nil
   136  	}
   137  	warnUnsupportedFeatures(stderr, c)
   138  	serviceConfigs := make([]v1beta2.ServiceConfig, len(c.Services))
   139  	for i, s := range c.Services {
   140  		serviceConfigs[i] = fromComposeServiceConfig(s)
   141  	}
   142  	return &v1beta2.StackSpec{
   143  		Services: serviceConfigs,
   144  		Secrets:  fromComposeSecrets(c.Secrets),
   145  		Configs:  fromComposeConfigs(c.Configs),
   146  	}
   147  }
   148  
   149  func fromComposeSecrets(s map[string]composeTypes.SecretConfig) map[string]v1beta2.SecretConfig {
   150  	if s == nil {
   151  		return nil
   152  	}
   153  	m := map[string]v1beta2.SecretConfig{}
   154  	for key, value := range s {
   155  		m[key] = v1beta2.SecretConfig{
   156  			Name: value.Name,
   157  			File: value.File,
   158  			External: v1beta2.External{
   159  				Name:     value.External.Name,
   160  				External: value.External.External,
   161  			},
   162  			Labels: value.Labels,
   163  		}
   164  	}
   165  	return m
   166  }
   167  
   168  func fromComposeConfigs(s map[string]composeTypes.ConfigObjConfig) map[string]v1beta2.ConfigObjConfig {
   169  	if s == nil {
   170  		return nil
   171  	}
   172  	m := map[string]v1beta2.ConfigObjConfig{}
   173  	for key, value := range s {
   174  		m[key] = v1beta2.ConfigObjConfig{
   175  			Name: value.Name,
   176  			File: value.File,
   177  			External: v1beta2.External{
   178  				Name:     value.External.Name,
   179  				External: value.External.External,
   180  			},
   181  			Labels: value.Labels,
   182  		}
   183  	}
   184  	return m
   185  }
   186  
   187  func fromComposeServiceConfig(s composeTypes.ServiceConfig) v1beta2.ServiceConfig {
   188  	var userID *int64
   189  	if s.User != "" {
   190  		numerical, err := strconv.Atoi(s.User)
   191  		if err == nil {
   192  			unixUserID := int64(numerical)
   193  			userID = &unixUserID
   194  		}
   195  	}
   196  	return v1beta2.ServiceConfig{
   197  		Name:    s.Name,
   198  		CapAdd:  s.CapAdd,
   199  		CapDrop: s.CapDrop,
   200  		Command: s.Command,
   201  		Configs: fromComposeServiceConfigs(s.Configs),
   202  		Deploy: v1beta2.DeployConfig{
   203  			Mode:          s.Deploy.Mode,
   204  			Replicas:      s.Deploy.Replicas,
   205  			Labels:        s.Deploy.Labels,
   206  			UpdateConfig:  fromComposeUpdateConfig(s.Deploy.UpdateConfig),
   207  			Resources:     fromComposeResources(s.Deploy.Resources),
   208  			RestartPolicy: fromComposeRestartPolicy(s.Deploy.RestartPolicy),
   209  			Placement:     fromComposePlacement(s.Deploy.Placement),
   210  		},
   211  		Entrypoint:      s.Entrypoint,
   212  		Environment:     s.Environment,
   213  		ExtraHosts:      s.ExtraHosts,
   214  		Hostname:        s.Hostname,
   215  		HealthCheck:     fromComposeHealthcheck(s.HealthCheck),
   216  		Image:           s.Image,
   217  		Ipc:             s.Ipc,
   218  		Labels:          s.Labels,
   219  		Pid:             s.Pid,
   220  		Ports:           fromComposePorts(s.Ports),
   221  		Privileged:      s.Privileged,
   222  		ReadOnly:        s.ReadOnly,
   223  		Secrets:         fromComposeServiceSecrets(s.Secrets),
   224  		StdinOpen:       s.StdinOpen,
   225  		StopGracePeriod: composetypes.ConvertDurationPtr(s.StopGracePeriod),
   226  		Tmpfs:           s.Tmpfs,
   227  		Tty:             s.Tty,
   228  		User:            userID,
   229  		Volumes:         fromComposeServiceVolumeConfig(s.Volumes),
   230  		WorkingDir:      s.WorkingDir,
   231  	}
   232  }
   233  
   234  func fromComposePorts(ports []composeTypes.ServicePortConfig) []v1beta2.ServicePortConfig {
   235  	if ports == nil {
   236  		return nil
   237  	}
   238  	p := make([]v1beta2.ServicePortConfig, len(ports))
   239  	for i, port := range ports {
   240  		p[i] = v1beta2.ServicePortConfig{
   241  			Mode:      port.Mode,
   242  			Target:    port.Target,
   243  			Published: port.Published,
   244  			Protocol:  port.Protocol,
   245  		}
   246  	}
   247  	return p
   248  }
   249  
   250  func fromComposeServiceSecrets(secrets []composeTypes.ServiceSecretConfig) []v1beta2.ServiceSecretConfig {
   251  	if secrets == nil {
   252  		return nil
   253  	}
   254  	c := make([]v1beta2.ServiceSecretConfig, len(secrets))
   255  	for i, secret := range secrets {
   256  		c[i] = v1beta2.ServiceSecretConfig{
   257  			Source: secret.Source,
   258  			Target: secret.Target,
   259  			UID:    secret.UID,
   260  			Mode:   secret.Mode,
   261  		}
   262  	}
   263  	return c
   264  }
   265  
   266  func fromComposeServiceConfigs(configs []composeTypes.ServiceConfigObjConfig) []v1beta2.ServiceConfigObjConfig {
   267  	if configs == nil {
   268  		return nil
   269  	}
   270  	c := make([]v1beta2.ServiceConfigObjConfig, len(configs))
   271  	for i, config := range configs {
   272  		c[i] = v1beta2.ServiceConfigObjConfig{
   273  			Source: config.Source,
   274  			Target: config.Target,
   275  			UID:    config.UID,
   276  			Mode:   config.Mode,
   277  		}
   278  	}
   279  	return c
   280  }
   281  
   282  func fromComposeHealthcheck(h *composeTypes.HealthCheckConfig) *v1beta2.HealthCheckConfig {
   283  	if h == nil {
   284  		return nil
   285  	}
   286  	return &v1beta2.HealthCheckConfig{
   287  		Test:     h.Test,
   288  		Timeout:  composetypes.ConvertDurationPtr(h.Timeout),
   289  		Interval: composetypes.ConvertDurationPtr(h.Interval),
   290  		Retries:  h.Retries,
   291  	}
   292  }
   293  
   294  func fromComposePlacement(p composeTypes.Placement) v1beta2.Placement {
   295  	return v1beta2.Placement{
   296  		Constraints: fromComposeConstraints(p.Constraints),
   297  	}
   298  }
   299  
   300  var constraintEquals = regexp.MustCompile(`([\w\.]*)\W*(==|!=)\W*([\w\.]*)`)
   301  
   302  const (
   303  	swarmOs          = "node.platform.os"
   304  	swarmArch        = "node.platform.arch"
   305  	swarmHostname    = "node.hostname"
   306  	swarmLabelPrefix = "node.labels."
   307  )
   308  
   309  func fromComposeConstraints(s []string) *v1beta2.Constraints {
   310  	if len(s) == 0 {
   311  		return nil
   312  	}
   313  	constraints := &v1beta2.Constraints{}
   314  	for _, constraint := range s {
   315  		matches := constraintEquals.FindStringSubmatch(constraint)
   316  		if len(matches) == 4 {
   317  			key := matches[1]
   318  			operator := matches[2]
   319  			value := matches[3]
   320  			constraint := &v1beta2.Constraint{
   321  				Operator: operator,
   322  				Value:    value,
   323  			}
   324  			switch {
   325  			case key == swarmOs:
   326  				constraints.OperatingSystem = constraint
   327  			case key == swarmArch:
   328  				constraints.Architecture = constraint
   329  			case key == swarmHostname:
   330  				constraints.Hostname = constraint
   331  			case strings.HasPrefix(key, swarmLabelPrefix):
   332  				if constraints.MatchLabels == nil {
   333  					constraints.MatchLabels = map[string]v1beta2.Constraint{}
   334  				}
   335  				constraints.MatchLabels[strings.TrimPrefix(key, swarmLabelPrefix)] = *constraint
   336  			}
   337  		}
   338  	}
   339  	return constraints
   340  }
   341  
   342  func fromComposeResources(r composeTypes.Resources) v1beta2.Resources {
   343  	return v1beta2.Resources{
   344  		Limits:       fromComposeResourcesResource(r.Limits),
   345  		Reservations: fromComposeResourcesResource(r.Reservations),
   346  	}
   347  }
   348  
   349  func fromComposeResourcesResource(r *composeTypes.Resource) *v1beta2.Resource {
   350  	if r == nil {
   351  		return nil
   352  	}
   353  	return &v1beta2.Resource{
   354  		MemoryBytes: int64(r.MemoryBytes),
   355  		NanoCPUs:    r.NanoCPUs,
   356  	}
   357  }
   358  
   359  func fromComposeUpdateConfig(u *composeTypes.UpdateConfig) *v1beta2.UpdateConfig {
   360  	if u == nil {
   361  		return nil
   362  	}
   363  	return &v1beta2.UpdateConfig{
   364  		Parallelism: u.Parallelism,
   365  	}
   366  }
   367  
   368  func fromComposeRestartPolicy(r *composeTypes.RestartPolicy) *v1beta2.RestartPolicy {
   369  	if r == nil {
   370  		return nil
   371  	}
   372  	return &v1beta2.RestartPolicy{
   373  		Condition: r.Condition,
   374  	}
   375  }
   376  
   377  func fromComposeServiceVolumeConfig(vs []composeTypes.ServiceVolumeConfig) []v1beta2.ServiceVolumeConfig {
   378  	if vs == nil {
   379  		return nil
   380  	}
   381  	volumes := []v1beta2.ServiceVolumeConfig{}
   382  	for _, v := range vs {
   383  		volumes = append(volumes, v1beta2.ServiceVolumeConfig{
   384  			Type:     v.Type,
   385  			Source:   v.Source,
   386  			Target:   v.Target,
   387  			ReadOnly: v.ReadOnly,
   388  		})
   389  	}
   390  	return volumes
   391  }