mirror of https://github.com/docker/cli.git
Print warnings on stderr for each unsupported features while parsing a compose file for deployment on Kubernetes.
Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
This commit is contained in:
parent
7ad30360c8
commit
36591a2282
|
@ -1,6 +1,8 @@
|
||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -36,7 +38,7 @@ func stackFromV1beta1(in *v1beta1.Stack) (stack, error) {
|
||||||
return stack{
|
return stack{
|
||||||
name: in.ObjectMeta.Name,
|
name: in.ObjectMeta.Name,
|
||||||
composeFile: in.Spec.ComposeFile,
|
composeFile: in.Spec.ComposeFile,
|
||||||
spec: fromComposeConfig(cfg),
|
spec: fromComposeConfig(ioutil.Discard, cfg),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,10 +69,11 @@ func stackToV1beta2(s stack) *v1beta2.Stack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromComposeConfig(c *composeTypes.Config) *v1beta2.StackSpec {
|
func fromComposeConfig(stderr io.Writer, c *composeTypes.Config) *v1beta2.StackSpec {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
warnUnsupportedFeatures(stderr, c)
|
||||||
serviceConfigs := make([]v1beta2.ServiceConfig, len(c.Services))
|
serviceConfigs := make([]v1beta2.ServiceConfig, len(c.Services))
|
||||||
for i, s := range c.Services {
|
for i, s := range c.Services {
|
||||||
serviceConfigs[i] = fromComposeServiceConfig(s)
|
serviceConfigs[i] = fromComposeServiceConfig(s)
|
||||||
|
|
|
@ -31,7 +31,7 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
stack, err := stacks.FromCompose(opts.Namespace, *cfg)
|
stack, err := stacks.FromCompose(dockerCli.Err(), opts.Namespace, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
composev1beta1 "github.com/docker/cli/kubernetes/client/clientset/typed/compose/v1beta1"
|
composev1beta1 "github.com/docker/cli/kubernetes/client/clientset/typed/compose/v1beta1"
|
||||||
|
@ -20,7 +21,7 @@ type stackClient interface {
|
||||||
Get(name string) (stack, error)
|
Get(name string) (stack, error)
|
||||||
List(opts metav1.ListOptions) ([]stack, error)
|
List(opts metav1.ListOptions) ([]stack, error)
|
||||||
IsColliding(servicesClient corev1.ServiceInterface, s stack) error
|
IsColliding(servicesClient corev1.ServiceInterface, s stack) error
|
||||||
FromCompose(name string, cfg composetypes.Config) (stack, error)
|
FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (stack, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stackV1Beta1 implements stackClient interface and talks to compose component v1beta1.
|
// stackV1Beta1 implements stackClient interface and talks to compose component v1beta1.
|
||||||
|
@ -103,16 +104,17 @@ func verify(services corev1.ServiceInterface, stackName string, service string)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stackV1Beta1) FromCompose(name string, cfg composetypes.Config) (stack, error) {
|
func (s *stackV1Beta1) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (stack, error) {
|
||||||
|
st, err := fromCompose(stderr, name, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return stack{}, err
|
||||||
|
}
|
||||||
res, err := yaml.Marshal(cfg)
|
res, err := yaml.Marshal(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stack{}, err
|
return stack{}, err
|
||||||
}
|
}
|
||||||
return stack{
|
st.composeFile = string(res)
|
||||||
name: name,
|
return st, nil
|
||||||
composeFile: string(res),
|
|
||||||
spec: fromComposeConfig(&cfg),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// stackV1Beta2 implements stackClient interface and talks to compose component v1beta2.
|
// stackV1Beta2 implements stackClient interface and talks to compose component v1beta2.
|
||||||
|
@ -169,9 +171,13 @@ func (s *stackV1Beta2) IsColliding(servicesClient corev1.ServiceInterface, st st
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stackV1Beta2) FromCompose(name string, cfg composetypes.Config) (stack, error) {
|
func (s *stackV1Beta2) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (stack, error) {
|
||||||
|
return fromCompose(stderr, name, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (stack, error) {
|
||||||
return stack{
|
return stack{
|
||||||
name: name,
|
name: name,
|
||||||
spec: fromComposeConfig(&cfg),
|
spec: fromComposeConfig(stderr, cfg),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
|
@ -9,7 +10,7 @@ import (
|
||||||
|
|
||||||
func TestFromCompose(t *testing.T) {
|
func TestFromCompose(t *testing.T) {
|
||||||
stackClient := &stackV1Beta1{}
|
stackClient := &stackV1Beta1{}
|
||||||
s, err := stackClient.FromCompose("foo", composetypes.Config{
|
s, err := stackClient.FromCompose(ioutil.Discard, "foo", &composetypes.Config{
|
||||||
Version: "3.1",
|
Version: "3.1",
|
||||||
Filename: "banana",
|
Filename: "banana",
|
||||||
Services: []composetypes.ServiceConfig{
|
Services: []composetypes.ServiceConfig{
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
top-level network "global" is ignored
|
||||||
|
service "front": network "private" is ignored
|
||||||
|
service "front": update_config.delay is not supported
|
||||||
|
service "front": update_config.failure_action is not supported
|
||||||
|
service "front": update_config.monitor is not supported
|
||||||
|
service "front": update_config.max_failure_ratio is not supported
|
||||||
|
service "front": restart_policy.delay is ignored
|
||||||
|
service "front": restart_policy.max_attempts is ignored
|
||||||
|
service "front": restart_policy.window is ignored
|
||||||
|
service "front": container_name is deprecated
|
||||||
|
service "front": expose is deprecated
|
||||||
|
service "front": build is ignored
|
||||||
|
service "front": cgroup_parent is ignored
|
||||||
|
service "front": devices are ignored
|
||||||
|
service "front": domainname is ignored
|
||||||
|
service "front": external_links are ignored
|
||||||
|
service "front": links are ignored
|
||||||
|
service "front": mac_address is ignored
|
||||||
|
service "front": network_mode is ignored
|
||||||
|
service "front": restart is ignored
|
||||||
|
service "front": security_opt are ignored
|
||||||
|
service "front": ulimits are ignored
|
||||||
|
service "front": depends_on are ignored
|
||||||
|
service "front": credential_spec is ignored
|
||||||
|
service "front": dns are ignored
|
||||||
|
service "front": dns_search are ignored
|
||||||
|
service "front": env_file are ignored
|
||||||
|
service "front": stop_signal is ignored
|
||||||
|
service "front": logging is ignored
|
||||||
|
service "front": volume.propagation is ignored
|
||||||
|
service "front": volume.nocopy is ignored
|
|
@ -0,0 +1,145 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func warnUnsupportedFeatures(stderr io.Writer, cfg *composetypes.Config) {
|
||||||
|
warnForGlobalNetworks(stderr, cfg)
|
||||||
|
for _, s := range cfg.Services {
|
||||||
|
warnForServiceNetworks(stderr, s)
|
||||||
|
warnForUnsupportedDeploymentStrategy(stderr, s)
|
||||||
|
warnForUnsupportedRestartPolicy(stderr, s)
|
||||||
|
warnForDeprecatedProperties(stderr, s)
|
||||||
|
warnForUnsupportedProperties(stderr, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnForGlobalNetworks(stderr io.Writer, config *composetypes.Config) {
|
||||||
|
for network := range config.Networks {
|
||||||
|
fmt.Fprintf(stderr, "top-level network %q is ignored\n", network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnServicef(stderr io.Writer, service, format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(stderr, "service \"%s\": %s\n", service, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnForServiceNetworks(stderr io.Writer, s composetypes.ServiceConfig) {
|
||||||
|
for network := range s.Networks {
|
||||||
|
warnServicef(stderr, s.Name, "network %q is ignored", network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnForDeprecatedProperties(stderr io.Writer, s composetypes.ServiceConfig) {
|
||||||
|
if s.ContainerName != "" {
|
||||||
|
warnServicef(stderr, s.Name, "container_name is deprecated")
|
||||||
|
}
|
||||||
|
if len(s.Expose) > 0 {
|
||||||
|
warnServicef(stderr, s.Name, "expose is deprecated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnForUnsupportedDeploymentStrategy(stderr io.Writer, s composetypes.ServiceConfig) {
|
||||||
|
config := s.Deploy.UpdateConfig
|
||||||
|
if config == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if config.Delay != 0 {
|
||||||
|
warnServicef(stderr, s.Name, "update_config.delay is not supported")
|
||||||
|
}
|
||||||
|
if config.FailureAction != "" {
|
||||||
|
warnServicef(stderr, s.Name, "update_config.failure_action is not supported")
|
||||||
|
}
|
||||||
|
if config.Monitor != 0 {
|
||||||
|
warnServicef(stderr, s.Name, "update_config.monitor is not supported")
|
||||||
|
}
|
||||||
|
if config.MaxFailureRatio != 0 {
|
||||||
|
warnServicef(stderr, s.Name, "update_config.max_failure_ratio is not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnForUnsupportedRestartPolicy(stderr io.Writer, s composetypes.ServiceConfig) {
|
||||||
|
policy := s.Deploy.RestartPolicy
|
||||||
|
if policy == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if policy.Delay != nil {
|
||||||
|
warnServicef(stderr, s.Name, "restart_policy.delay is ignored")
|
||||||
|
}
|
||||||
|
if policy.MaxAttempts != nil {
|
||||||
|
warnServicef(stderr, s.Name, "restart_policy.max_attempts is ignored")
|
||||||
|
}
|
||||||
|
if policy.Window != nil {
|
||||||
|
warnServicef(stderr, s.Name, "restart_policy.window is ignored")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnForUnsupportedProperties(stderr io.Writer, s composetypes.ServiceConfig) { // nolint: gocyclo
|
||||||
|
if build := s.Build; build.Context != "" || build.Dockerfile != "" || len(build.Args) > 0 || len(build.Labels) > 0 || len(build.CacheFrom) > 0 || build.Network != "" || build.Target != "" {
|
||||||
|
warnServicef(stderr, s.Name, "build is ignored")
|
||||||
|
}
|
||||||
|
if s.CgroupParent != "" {
|
||||||
|
warnServicef(stderr, s.Name, "cgroup_parent is ignored")
|
||||||
|
}
|
||||||
|
if len(s.Devices) > 0 {
|
||||||
|
warnServicef(stderr, s.Name, "devices are ignored")
|
||||||
|
}
|
||||||
|
if s.DomainName != "" {
|
||||||
|
warnServicef(stderr, s.Name, "domainname is ignored")
|
||||||
|
}
|
||||||
|
if len(s.ExternalLinks) > 0 {
|
||||||
|
warnServicef(stderr, s.Name, "external_links are ignored")
|
||||||
|
}
|
||||||
|
if len(s.Links) > 0 {
|
||||||
|
warnServicef(stderr, s.Name, "links are ignored")
|
||||||
|
}
|
||||||
|
if s.MacAddress != "" {
|
||||||
|
warnServicef(stderr, s.Name, "mac_address is ignored")
|
||||||
|
}
|
||||||
|
if s.NetworkMode != "" {
|
||||||
|
warnServicef(stderr, s.Name, "network_mode is ignored")
|
||||||
|
}
|
||||||
|
if s.Restart != "" {
|
||||||
|
warnServicef(stderr, s.Name, "restart is ignored")
|
||||||
|
}
|
||||||
|
if len(s.SecurityOpt) > 0 {
|
||||||
|
warnServicef(stderr, s.Name, "security_opt are ignored")
|
||||||
|
}
|
||||||
|
if len(s.Ulimits) > 0 {
|
||||||
|
warnServicef(stderr, s.Name, "ulimits are ignored")
|
||||||
|
}
|
||||||
|
if len(s.DependsOn) > 0 {
|
||||||
|
warnServicef(stderr, s.Name, "depends_on are ignored")
|
||||||
|
}
|
||||||
|
if s.CredentialSpec.File != "" {
|
||||||
|
warnServicef(stderr, s.Name, "credential_spec is ignored")
|
||||||
|
}
|
||||||
|
if len(s.DNS) > 0 {
|
||||||
|
warnServicef(stderr, s.Name, "dns are ignored")
|
||||||
|
}
|
||||||
|
if len(s.DNSSearch) > 0 {
|
||||||
|
warnServicef(stderr, s.Name, "dns_search are ignored")
|
||||||
|
}
|
||||||
|
if len(s.EnvFile) > 0 {
|
||||||
|
warnServicef(stderr, s.Name, "env_file are ignored")
|
||||||
|
}
|
||||||
|
if s.StopSignal != "" {
|
||||||
|
warnServicef(stderr, s.Name, "stop_signal is ignored")
|
||||||
|
}
|
||||||
|
if s.Logging != nil {
|
||||||
|
warnServicef(stderr, s.Name, "logging is ignored")
|
||||||
|
}
|
||||||
|
for _, m := range s.Volumes {
|
||||||
|
if m.Volume != nil && m.Volume.NoCopy {
|
||||||
|
warnServicef(stderr, s.Name, "volume.nocopy is ignored")
|
||||||
|
}
|
||||||
|
if m.Bind != nil && m.Bind.Propagation != "" {
|
||||||
|
warnServicef(stderr, s.Name, "volume.propagation is ignored")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
|
"github.com/gotestyourself/gotestyourself/golden"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWarnings(t *testing.T) {
|
||||||
|
duration := 5 * time.Second
|
||||||
|
attempts := uint64(3)
|
||||||
|
config := &composetypes.Config{
|
||||||
|
Version: "3.4",
|
||||||
|
Services: []composetypes.ServiceConfig{
|
||||||
|
{
|
||||||
|
Name: "front",
|
||||||
|
Build: composetypes.BuildConfig{
|
||||||
|
Context: "ignored",
|
||||||
|
},
|
||||||
|
ContainerName: "ignored",
|
||||||
|
CgroupParent: "ignored",
|
||||||
|
CredentialSpec: composetypes.CredentialSpecConfig{File: "ignored"},
|
||||||
|
DependsOn: []string{"ignored"},
|
||||||
|
Deploy: composetypes.DeployConfig{
|
||||||
|
UpdateConfig: &composetypes.UpdateConfig{
|
||||||
|
Delay: 5 * time.Second,
|
||||||
|
FailureAction: "rollback",
|
||||||
|
Monitor: 10 * time.Second,
|
||||||
|
MaxFailureRatio: 0.5,
|
||||||
|
},
|
||||||
|
RestartPolicy: &composetypes.RestartPolicy{
|
||||||
|
Delay: &duration,
|
||||||
|
MaxAttempts: &attempts,
|
||||||
|
Window: &duration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Devices: []string{"ignored"},
|
||||||
|
DNSSearch: []string{"ignored"},
|
||||||
|
DNS: []string{"ignored"},
|
||||||
|
DomainName: "ignored",
|
||||||
|
EnvFile: []string{"ignored"},
|
||||||
|
Expose: []string{"80"},
|
||||||
|
ExternalLinks: []string{"ignored"},
|
||||||
|
Image: "dockerdemos/front",
|
||||||
|
Links: []string{"ignored"},
|
||||||
|
Logging: &composetypes.LoggingConfig{Driver: "syslog"},
|
||||||
|
MacAddress: "ignored",
|
||||||
|
Networks: map[string]*composetypes.ServiceNetworkConfig{"private": {}},
|
||||||
|
NetworkMode: "ignored",
|
||||||
|
Restart: "ignored",
|
||||||
|
SecurityOpt: []string{"ignored"},
|
||||||
|
StopSignal: "ignored",
|
||||||
|
Ulimits: map[string]*composetypes.UlimitsConfig{"nproc": {Hard: 65535}},
|
||||||
|
User: "ignored",
|
||||||
|
Volumes: []composetypes.ServiceVolumeConfig{
|
||||||
|
{
|
||||||
|
Type: "bind",
|
||||||
|
Bind: &composetypes.ServiceVolumeBind{Propagation: "ignored"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "volume",
|
||||||
|
Volume: &composetypes.ServiceVolumeVolume{NoCopy: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Networks: map[string]composetypes.NetworkConfig{
|
||||||
|
"global": {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
warnUnsupportedFeatures(&buf, config)
|
||||||
|
warnings := buf.String()
|
||||||
|
golden.Assert(t, warnings, "warnings.golden")
|
||||||
|
}
|
|
@ -75,7 +75,7 @@ func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert
|
||||||
|
|
||||||
oldServices, err := getServices(ctx, client, namespace.Name())
|
oldServices, err := getServices(ctx, client, namespace.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(dockerCli.Err(), "Failed to list services: %s", err)
|
fmt.Fprintf(dockerCli.Err(), "Failed to list services: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pruneServices := []swarm.Service{}
|
pruneServices := []swarm.Service{}
|
||||||
|
|
Loading…
Reference in New Issue