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 }