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
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
|
@ -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{
|
||||
|
|
|
@ -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())
|
||||
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{}
|
||||
|
|
Loading…
Reference in New Issue