github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/service/parse.go (about) 1 package service 2 3 import ( 4 "context" 5 6 "github.com/docker/docker/api/types" 7 "github.com/docker/docker/api/types/filters" 8 swarmtypes "github.com/docker/docker/api/types/swarm" 9 "github.com/docker/docker/client" 10 "github.com/pkg/errors" 11 ) 12 13 // ParseSecrets retrieves the secrets with the requested names and fills 14 // secret IDs into the secret references. 15 func ParseSecrets(ctx context.Context, apiClient client.SecretAPIClient, requestedSecrets []*swarmtypes.SecretReference) ([]*swarmtypes.SecretReference, error) { 16 if len(requestedSecrets) == 0 { 17 return []*swarmtypes.SecretReference{}, nil 18 } 19 20 secretRefs := make(map[string]*swarmtypes.SecretReference) 21 22 for _, secret := range requestedSecrets { 23 if _, exists := secretRefs[secret.File.Name]; exists { 24 return nil, errors.Errorf("duplicate secret target for %s not allowed", secret.SecretName) 25 } 26 secretRef := new(swarmtypes.SecretReference) 27 *secretRef = *secret 28 secretRefs[secret.File.Name] = secretRef 29 } 30 31 args := filters.NewArgs() 32 for _, s := range secretRefs { 33 args.Add("name", s.SecretName) 34 } 35 36 secrets, err := apiClient.SecretList(ctx, types.SecretListOptions{ 37 Filters: args, 38 }) 39 if err != nil { 40 return nil, err 41 } 42 43 foundSecrets := make(map[string]string) 44 for _, secret := range secrets { 45 foundSecrets[secret.Spec.Annotations.Name] = secret.ID 46 } 47 48 addedSecrets := []*swarmtypes.SecretReference{} 49 50 for _, ref := range secretRefs { 51 id, ok := foundSecrets[ref.SecretName] 52 if !ok { 53 return nil, errors.Errorf("secret not found: %s", ref.SecretName) 54 } 55 56 // set the id for the ref to properly assign in swarm 57 // since swarm needs the ID instead of the name 58 ref.SecretID = id 59 addedSecrets = append(addedSecrets, ref) 60 } 61 62 return addedSecrets, nil 63 } 64 65 // ParseConfigs retrieves the configs from the requested names and converts 66 // them to config references to use with the spec 67 func ParseConfigs(ctx context.Context, apiClient client.ConfigAPIClient, requestedConfigs []*swarmtypes.ConfigReference) ([]*swarmtypes.ConfigReference, error) { 68 if len(requestedConfigs) == 0 { 69 return []*swarmtypes.ConfigReference{}, nil 70 } 71 72 // the configRefs map has two purposes: it prevents duplication of config 73 // target filenames. It is used to get all configs, so we can resolve 74 // their IDs. unfortunately, there are other targets for ConfigReferences, 75 // besides just a File; specifically, the Runtime target, which is used for 76 // CredentialSpecs. Therefore, we need to have a list of ConfigReferences 77 // that are not File targets as well. at this time of writing, the only use 78 // for Runtime targets is CredentialSpecs. However, to future-proof this 79 // functionality, we should handle the case where multiple Runtime targets 80 // are in use for the same Config, and we should deduplicate 81 // such ConfigReferences, as no matter how many times the Config is used, 82 // it is only needed to be referenced once. 83 configRefs := make(map[string]*swarmtypes.ConfigReference) 84 runtimeRefs := make(map[string]*swarmtypes.ConfigReference) 85 86 for _, config := range requestedConfigs { 87 // copy the config, so we don't mutate the args 88 configRef := new(swarmtypes.ConfigReference) 89 *configRef = *config 90 91 if config.Runtime != nil { 92 // by assigning to a map based on ConfigName, if the same Config 93 // is required as a Runtime target for multiple purposes, we only 94 // include it once in the final set of configs. 95 runtimeRefs[config.ConfigName] = config 96 // continue, so we skip the logic below for handling file-type 97 // configs 98 continue 99 } 100 101 if _, exists := configRefs[config.File.Name]; exists { 102 return nil, errors.Errorf("duplicate config target for %s not allowed", config.ConfigName) 103 } 104 105 configRefs[config.File.Name] = configRef 106 } 107 108 args := filters.NewArgs() 109 for _, s := range configRefs { 110 args.Add("name", s.ConfigName) 111 } 112 for _, s := range runtimeRefs { 113 args.Add("name", s.ConfigName) 114 } 115 116 configs, err := apiClient.ConfigList(ctx, types.ConfigListOptions{ 117 Filters: args, 118 }) 119 if err != nil { 120 return nil, err 121 } 122 123 foundConfigs := make(map[string]string) 124 for _, config := range configs { 125 foundConfigs[config.Spec.Annotations.Name] = config.ID 126 } 127 128 addedConfigs := []*swarmtypes.ConfigReference{} 129 130 for _, ref := range configRefs { 131 id, ok := foundConfigs[ref.ConfigName] 132 if !ok { 133 return nil, errors.Errorf("config not found: %s", ref.ConfigName) 134 } 135 136 // set the id for the ref to properly assign in swarm 137 // since swarm needs the ID instead of the name 138 ref.ConfigID = id 139 addedConfigs = append(addedConfigs, ref) 140 } 141 142 // unfortunately, because the key of configRefs and runtimeRefs is different 143 // values that may collide, we can't just do some fancy trickery to 144 // concat maps, we need to do two separate loops 145 for _, ref := range runtimeRefs { 146 id, ok := foundConfigs[ref.ConfigName] 147 if !ok { 148 return nil, errors.Errorf("config not found: %s", ref.ConfigName) 149 } 150 151 ref.ConfigID = id 152 addedConfigs = append(addedConfigs, ref) 153 } 154 155 return addedConfigs, nil 156 }