Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add warnings client side before deployment on Kubernetes #903

Merged
merged 1 commit into from
Apr 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions cli/command/stack/kubernetes/convert.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package kubernetes

import (
"io"
"io/ioutil"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -36,7 +38,7 @@ func stackFromV1beta1(in *v1beta1.Stack) (stack, error) {
return stack{
name: in.ObjectMeta.Name,
composeFile: in.Spec.ComposeFile,
spec: fromComposeConfig(cfg),
spec: fromComposeConfig(ioutil.Discard, cfg),
}, nil
}

Expand Down Expand Up @@ -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 {
return nil
}
warnUnsupportedFeatures(stderr, c)
serviceConfigs := make([]v1beta2.ServiceConfig, len(c.Services))
for i, s := range c.Services {
serviceConfigs[i] = fromComposeServiceConfig(s)
Expand Down
2 changes: 1 addition & 1 deletion cli/command/stack/kubernetes/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
if err != nil {
return err
}
stack, err := stacks.FromCompose(opts.Namespace, *cfg)
stack, err := stacks.FromCompose(dockerCli.Err(), opts.Namespace, cfg)
if err != nil {
return err
}
Expand Down
24 changes: 15 additions & 9 deletions cli/command/stack/kubernetes/stackclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package kubernetes

import (
"fmt"
"io"

composetypes "github.com/docker/cli/cli/compose/types"
composev1beta1 "github.com/docker/cli/kubernetes/client/clientset/typed/compose/v1beta1"
Expand All @@ -20,7 +21,7 @@ type stackClient interface {
Get(name string) (stack, error)
List(opts metav1.ListOptions) ([]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.
Expand Down Expand Up @@ -103,16 +104,17 @@ func verify(services corev1.ServiceInterface, stackName string, service string)
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)
if err != nil {
return stack{}, err
}
return stack{
name: name,
composeFile: string(res),
spec: fromComposeConfig(&cfg),
}, nil
st.composeFile = string(res)
return st, nil
}

// stackV1Beta2 implements stackClient interface and talks to compose component v1beta2.
Expand Down Expand Up @@ -169,9 +171,13 @@ func (s *stackV1Beta2) IsColliding(servicesClient corev1.ServiceInterface, st st
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{
name: name,
spec: fromComposeConfig(&cfg),
spec: fromComposeConfig(stderr, cfg),
}, nil
}
3 changes: 2 additions & 1 deletion cli/command/stack/kubernetes/stackclient_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kubernetes

import (
"io/ioutil"
"testing"

composetypes "github.com/docker/cli/cli/compose/types"
Expand All @@ -9,7 +10,7 @@ import (

func TestFromCompose(t *testing.T) {
stackClient := &stackV1Beta1{}
s, err := stackClient.FromCompose("foo", composetypes.Config{
s, err := stackClient.FromCompose(ioutil.Discard, "foo", &composetypes.Config{
Version: "3.1",
Filename: "banana",
Services: []composetypes.ServiceConfig{
Expand Down
31 changes: 31 additions & 0 deletions cli/command/stack/kubernetes/testdata/warnings.golden
Original file line number Diff line number Diff line change
@@ -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
145 changes: 145 additions & 0 deletions cli/command/stack/kubernetes/warnings.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
}
78 changes: 78 additions & 0 deletions cli/command/stack/kubernetes/warnings_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
2 changes: 1 addition & 1 deletion cli/command/stack/swarm/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert

oldServices, err := getServices(ctx, client, namespace.Name())
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{}
Expand Down