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:
Silvin Lubecki 2018-02-26 15:59:44 +01:00 committed by Mathieu Champlon
parent 7ad30360c8
commit 36591a2282
8 changed files with 278 additions and 14 deletions

View File

@ -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)

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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{

View File

@ -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

View File

@ -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")
}
}
}

View File

@ -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")
}

View File

@ -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{}