github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/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  }