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