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