Skip to content

Commit

Permalink
Merge pull request moby#6 from aanand/compose-on-swarm
Browse files Browse the repository at this point in the history
Handle unsupported, deprecated and forbidden properties
  • Loading branch information
dnephin committed Nov 1, 2016
2 parents 83e90fa + d7e2cbd commit a10ef7c
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 73 deletions.
30 changes: 29 additions & 1 deletion cli/command/stack/deploy.go
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"os"
"sort"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -62,16 +63,42 @@ func runDeploy(dockerCli *command.DockerCli, opts deployOptions) error {

config, err := loader.Load(configDetails)
if err != nil {
if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok {
return fmt.Errorf("Compose file contains unsupported options:\n\n%s\n",
propertyWarnings(fpe.Properties))
}

return err
}

unsupportedProperties := loader.GetUnsupportedProperties(configDetails)
if len(unsupportedProperties) > 0 {
fmt.Printf("Ignoring unsupported options: %s\n\n",
strings.Join(unsupportedProperties, ", "))
}

deprecatedProperties := loader.GetDeprecatedProperties(configDetails)
if len(deprecatedProperties) > 0 {
fmt.Printf("Ignoring deprecated options:\n\n%s\n\n",
propertyWarnings(deprecatedProperties))
}

ctx := context.Background()
if err := createNetworks(ctx, dockerCli, config.Networks, opts.namespace); err != nil {
return err
}
return deployServices(ctx, dockerCli, config, opts.namespace, opts.sendRegistryAuth)
}

func propertyWarnings(properties map[string]string) string {
var msgs []string
for name, description := range properties {
msgs = append(msgs, fmt.Sprintf("%s: %s", name, description))
}
sort.Strings(msgs)
return strings.Join(msgs, "\n\n")
}

func getConfigDetails(opts deployOptions) (composetypes.ConfigDetails, error) {
var details composetypes.ConfigDetails
var err error
Expand Down Expand Up @@ -407,10 +434,11 @@ func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*
}, nil
}
}
attempts := uint64(*source.MaxAttempts)
return &swarm.RestartPolicy{
Condition: swarm.RestartPolicyCondition(source.Condition),
Delay: source.Delay,
MaxAttempts: source.MaxAttempts,
MaxAttempts: &attempts,
Window: source.Window,
}, nil
}
Expand Down
Expand Up @@ -228,9 +228,6 @@ services:
# Named volume
- datavolume:/var/lib/mysql

# Only permitted if no host path volumes have been specified
volume_driver: mydriver

working_dir: /code

networks:
Expand Down
100 changes: 85 additions & 15 deletions vendor/src/github.com/aanand/compose-file/loader/loader.go
Expand Up @@ -6,6 +6,7 @@ import (
"path"
"reflect"
"regexp"
"sort"
"strings"

"github.com/aanand/compose-file/interpolation"
Expand Down Expand Up @@ -49,20 +50,29 @@ func Load(configDetails types.ConfigDetails) (*types.Config, error) {
return nil, fmt.Errorf("Multiple files are not yet supported")
}

// TODO: support multiple files
file := configDetails.ConfigFiles[0]
configDict := getConfigDict(configDetails)

if err := validateAgainstConfigSchema(file); err != nil {
if services, ok := configDict["services"]; ok {
if servicesDict, ok := services.(types.Dict); ok {
forbidden := getProperties(servicesDict, types.ForbiddenProperties)

if len(forbidden) > 0 {
return nil, &ForbiddenPropertiesError{Properties: forbidden}
}
}
}

if err := schema.Validate(configDict); err != nil {
return nil, err
}

cfg := types.Config{}
version := file.Config["version"].(string)
version := configDict["version"].(string)
if version != "3" {
return nil, fmt.Errorf("Unsupported version: %#v. The only supported version is 3", version)
}

if services, ok := file.Config["services"]; ok {
if services, ok := configDict["services"]; ok {
servicesConfig, err := interpolation.Interpolate(services.(types.Dict), "service", os.LookupEnv)
if err != nil {
return nil, err
Expand All @@ -76,7 +86,7 @@ func Load(configDetails types.ConfigDetails) (*types.Config, error) {
cfg.Services = servicesList
}

if networks, ok := file.Config["networks"]; ok {
if networks, ok := configDict["networks"]; ok {
networksConfig, err := interpolation.Interpolate(networks.(types.Dict), "network", os.LookupEnv)
if err != nil {
return nil, err
Expand All @@ -90,7 +100,7 @@ func Load(configDetails types.ConfigDetails) (*types.Config, error) {
cfg.Networks = networksMapping
}

if volumes, ok := file.Config["volumes"]; ok {
if volumes, ok := configDict["volumes"]; ok {
volumesConfig, err := interpolation.Interpolate(volumes.(types.Dict), "volume", os.LookupEnv)
if err != nil {
return nil, err
Expand All @@ -107,6 +117,73 @@ func Load(configDetails types.ConfigDetails) (*types.Config, error) {
return &cfg, nil
}

func GetUnsupportedProperties(configDetails types.ConfigDetails) []string {
unsupported := map[string]bool{}

for _, service := range getServices(getConfigDict(configDetails)) {
serviceDict := service.(types.Dict)
for _, property := range types.UnsupportedProperties {
if _, isSet := serviceDict[property]; isSet {
unsupported[property] = true
}
}
}

return sortedKeys(unsupported)
}

func sortedKeys(set map[string]bool) []string {
var keys []string
for key, _ := range set {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}

func GetDeprecatedProperties(configDetails types.ConfigDetails) map[string]string {
return getProperties(getServices(getConfigDict(configDetails)), types.DeprecatedProperties)
}

func getProperties(services types.Dict, propertyMap map[string]string) map[string]string {
output := map[string]string{}

for _, service := range services {
if serviceDict, ok := service.(types.Dict); ok {
for property, description := range propertyMap {
if _, isSet := serviceDict[property]; isSet {
output[property] = description
}
}
}
}

return output
}

type ForbiddenPropertiesError struct {
Properties map[string]string
}

func (e *ForbiddenPropertiesError) Error() string {
return "Configuration contains forbidden properties"
}

// TODO: resolve multiple files into a single config
func getConfigDict(configDetails types.ConfigDetails) types.Dict {
return configDetails.ConfigFiles[0].Config
}

func getServices(configDict types.Dict) types.Dict {
if services, ok := configDict["services"]; ok {
if servicesDict, ok := services.(types.Dict); ok {
return servicesDict
}
}

return types.Dict{}
}

func transform(source map[string]interface{}, target interface{}) error {
data := mapstructure.Metadata{}
config := &mapstructure.DecoderConfig{
Expand All @@ -121,10 +198,7 @@ func transform(source map[string]interface{}, target interface{}) error {
return err
}
err = decoder.Decode(source)
// TODO: use logging
if len(data.Unused) > 0 {
fmt.Printf("Unused keys: %s", data.Unused)
}
// TODO: log unused keys
return err
}

Expand All @@ -150,10 +224,6 @@ func transformHook(
return data, nil
}

func validateAgainstConfigSchema(file types.ConfigFile) error {
return schema.Validate(file.Config)
}

// keys needs to be converted to strings for jsonschema
// TODO: don't use types.Dict
func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interface{}, error) {
Expand Down

0 comments on commit a10ef7c

Please sign in to comment.