github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/service/create.go (about) 1 package service 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/docker/cli/cli" 8 "github.com/docker/cli/cli/command" 9 cliopts "github.com/docker/cli/opts" 10 "github.com/docker/docker/api/types" 11 "github.com/docker/docker/api/types/swarm" 12 "github.com/docker/docker/api/types/versions" 13 "github.com/docker/docker/client" 14 "github.com/spf13/cobra" 15 "github.com/spf13/pflag" 16 ) 17 18 func newCreateCommand(dockerCli command.Cli) *cobra.Command { 19 opts := newServiceOptions() 20 21 cmd := &cobra.Command{ 22 Use: "create [OPTIONS] IMAGE [COMMAND] [ARG...]", 23 Short: "Create a new service", 24 Args: cli.RequiresMinArgs(1), 25 RunE: func(cmd *cobra.Command, args []string) error { 26 opts.image = args[0] 27 if len(args) > 1 { 28 opts.args = args[1:] 29 } 30 return runCreate(dockerCli, cmd.Flags(), opts) 31 }, 32 } 33 flags := cmd.Flags() 34 flags.StringVar(&opts.mode, flagMode, "replicated", "Service mode (replicated, global, replicated-job, or global-job)") 35 flags.StringVar(&opts.name, flagName, "", "Service name") 36 37 addServiceFlags(flags, opts, buildServiceDefaultFlagMapping()) 38 39 flags.VarP(&opts.labels, flagLabel, "l", "Service labels") 40 flags.Var(&opts.containerLabels, flagContainerLabel, "Container labels") 41 flags.VarP(&opts.env, flagEnv, "e", "Set environment variables") 42 flags.Var(&opts.envFile, flagEnvFile, "Read in a file of environment variables") 43 flags.Var(&opts.mounts, flagMount, "Attach a filesystem mount to the service") 44 flags.Var(&opts.constraints, flagConstraint, "Placement constraints") 45 flags.Var(&opts.placementPrefs, flagPlacementPref, "Add a placement preference") 46 flags.SetAnnotation(flagPlacementPref, "version", []string{"1.28"}) 47 flags.Var(&opts.networks, flagNetwork, "Network attachments") 48 flags.Var(&opts.secrets, flagSecret, "Specify secrets to expose to the service") 49 flags.SetAnnotation(flagSecret, "version", []string{"1.25"}) 50 flags.Var(&opts.configs, flagConfig, "Specify configurations to expose to the service") 51 flags.SetAnnotation(flagConfig, "version", []string{"1.30"}) 52 flags.VarP(&opts.endpoint.publishPorts, flagPublish, "p", "Publish a port as a node port") 53 flags.Var(&opts.groups, flagGroup, "Set one or more supplementary user groups for the container") 54 flags.SetAnnotation(flagGroup, "version", []string{"1.25"}) 55 flags.Var(&opts.dns, flagDNS, "Set custom DNS servers") 56 flags.SetAnnotation(flagDNS, "version", []string{"1.25"}) 57 flags.Var(&opts.dnsOption, flagDNSOption, "Set DNS options") 58 flags.SetAnnotation(flagDNSOption, "version", []string{"1.25"}) 59 flags.Var(&opts.dnsSearch, flagDNSSearch, "Set custom DNS search domains") 60 flags.SetAnnotation(flagDNSSearch, "version", []string{"1.25"}) 61 flags.Var(&opts.hosts, flagHost, "Set one or more custom host-to-IP mappings (host:ip)") 62 flags.SetAnnotation(flagHost, "version", []string{"1.25"}) 63 flags.BoolVar(&opts.init, flagInit, false, "Use an init inside each service container to forward signals and reap processes") 64 flags.SetAnnotation(flagInit, "version", []string{"1.37"}) 65 flags.Var(&opts.sysctls, flagSysCtl, "Sysctl options") 66 flags.SetAnnotation(flagSysCtl, "version", []string{"1.40"}) 67 flags.Var(&opts.ulimits, flagUlimit, "Ulimit options") 68 flags.SetAnnotation(flagUlimit, "version", []string{"1.41"}) 69 70 flags.Var(cliopts.NewListOptsRef(&opts.resources.resGenericResources, ValidateSingleGenericResource), "generic-resource", "User defined resources") 71 flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"}) 72 73 flags.SetInterspersed(false) 74 return cmd 75 } 76 77 func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *serviceOptions) error { 78 apiClient := dockerCli.Client() 79 createOpts := types.ServiceCreateOptions{} 80 81 ctx := context.Background() 82 83 service, err := opts.ToService(ctx, apiClient, flags) 84 if err != nil { 85 return err 86 } 87 88 if err = validateAPIVersion(service, dockerCli.Client().ClientVersion()); err != nil { 89 return err 90 } 91 92 specifiedSecrets := opts.secrets.Value() 93 if len(specifiedSecrets) > 0 { 94 // parse and validate secrets 95 secrets, err := ParseSecrets(apiClient, specifiedSecrets) 96 if err != nil { 97 return err 98 } 99 service.TaskTemplate.ContainerSpec.Secrets = secrets 100 } 101 102 if err := setConfigs(apiClient, &service, opts); err != nil { 103 return err 104 } 105 106 if err := resolveServiceImageDigestContentTrust(dockerCli, &service); err != nil { 107 return err 108 } 109 110 // only send auth if flag was set 111 if opts.registryAuth { 112 // Retrieve encoded auth token from the image reference 113 encodedAuth, err := command.RetrieveAuthTokenFromImage(ctx, dockerCli, opts.image) 114 if err != nil { 115 return err 116 } 117 createOpts.EncodedRegistryAuth = encodedAuth 118 } 119 120 // query registry if flag disabling it was not set 121 if !opts.noResolveImage && versions.GreaterThanOrEqualTo(apiClient.ClientVersion(), "1.30") { 122 createOpts.QueryRegistry = true 123 } 124 125 response, err := apiClient.ServiceCreate(ctx, service, createOpts) 126 if err != nil { 127 return err 128 } 129 130 for _, warning := range response.Warnings { 131 fmt.Fprintln(dockerCli.Err(), warning) 132 } 133 134 fmt.Fprintf(dockerCli.Out(), "%s\n", response.ID) 135 136 if opts.detach || versions.LessThan(apiClient.ClientVersion(), "1.29") { 137 return nil 138 } 139 140 return waitOnService(ctx, dockerCli, response.ID, opts.quiet) 141 } 142 143 // setConfigs does double duty: it both sets the ConfigReferences of the 144 // service, and it sets the service CredentialSpec. This is because there is an 145 // interplay between the CredentialSpec and the Config it depends on. 146 func setConfigs(apiClient client.ConfigAPIClient, service *swarm.ServiceSpec, opts *serviceOptions) error { 147 specifiedConfigs := opts.configs.Value() 148 // if the user has requested to use a Config, for the CredentialSpec add it 149 // to the specifiedConfigs as a RuntimeTarget. 150 if cs := opts.credentialSpec.Value(); cs != nil && cs.Config != "" { 151 specifiedConfigs = append(specifiedConfigs, &swarm.ConfigReference{ 152 ConfigName: cs.Config, 153 Runtime: &swarm.ConfigReferenceRuntimeTarget{}, 154 }) 155 } 156 if len(specifiedConfigs) > 0 { 157 // parse and validate configs 158 configs, err := ParseConfigs(apiClient, specifiedConfigs) 159 if err != nil { 160 return err 161 } 162 service.TaskTemplate.ContainerSpec.Configs = configs 163 // if we have a CredentialSpec Config, find its ID and rewrite the 164 // field on the spec 165 // 166 // we check the opts instead of the service directly because there are 167 // a few layers of nullable objects in the service, which is a PITA 168 // to traverse, but the existence of the option implies that those are 169 // non-null. 170 if cs := opts.credentialSpec.Value(); cs != nil && cs.Config != "" { 171 for _, config := range configs { 172 if config.ConfigName == cs.Config { 173 service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config = config.ConfigID 174 // we've found the right config, no need to keep iterating 175 // through the rest of them. 176 break 177 } 178 } 179 } 180 } 181 182 return nil 183 }