github.com/jdolitsky/cnab-go@v0.7.1-beta1/credentials/credentialset.go (about) 1 package credentials 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "os/exec" 8 "strings" 9 10 "github.com/deislabs/cnab-go/bundle" 11 12 yaml "gopkg.in/yaml.v2" 13 ) 14 15 // Set is an actual set of resolved credentials. 16 // This is the output of resolving a credentialset file. 17 type Set map[string]string 18 19 // Expand expands the set into env vars and paths per the spec in the bundle. 20 // 21 // This matches the credentials required by the bundle to the credentials present 22 // in the credentialset, and then expands them per the definition in the Bundle. 23 func (s Set) Expand(b *bundle.Bundle, stateless bool) (env, files map[string]string, err error) { 24 env, files = map[string]string{}, map[string]string{} 25 for name, val := range b.Credentials { 26 src, ok := s[name] 27 if !ok { 28 if stateless || !val.Required { 29 continue 30 } 31 err = fmt.Errorf("credential %q is missing from the user-supplied credentials", name) 32 return 33 } 34 if val.EnvironmentVariable != "" { 35 env[val.EnvironmentVariable] = src 36 } 37 if val.Path != "" { 38 files[val.Path] = src 39 } 40 } 41 return 42 } 43 44 // Merge merges a second Set into the base. 45 // 46 // Duplicate credential names are not allow and will result in an 47 // error, this is the case even if the values are identical. 48 func (s Set) Merge(s2 Set) error { 49 for k, v := range s2 { 50 if _, ok := s[k]; ok { 51 return fmt.Errorf("ambiguous credential resolution: %q is already present in base credential sets, cannot merge", k) 52 } 53 s[k] = v 54 } 55 return nil 56 } 57 58 // CredentialSet represents a collection of credentials 59 type CredentialSet struct { 60 // Name is the name of the credentialset. 61 Name string `json:"name" yaml:"name"` 62 // Creadentials is a list of credential specs. 63 Credentials []CredentialStrategy `json:"credentials" yaml:"credentials"` 64 } 65 66 // Load a CredentialSet from a file at a given path. 67 // 68 // It does not load the individual credentials. 69 func Load(path string) (*CredentialSet, error) { 70 cset := &CredentialSet{} 71 data, err := ioutil.ReadFile(path) 72 if err != nil { 73 return cset, err 74 } 75 return cset, yaml.Unmarshal(data, cset) 76 } 77 78 // Validate compares the given credentials with the spec. 79 // 80 // This will result in an error only when the following conditions are true: 81 // - a credential in the spec is not present in the given set 82 // - the credential is required 83 // 84 // It is allowed for spec to specify both an env var and a file. In such case, if 85 // the given set provides either, it will be considered valid. 86 func Validate(given Set, spec map[string]bundle.Credential) error { 87 for name, cred := range spec { 88 if !isValidCred(given, name) && cred.Required { 89 return fmt.Errorf("bundle requires credential for %s", name) 90 } 91 } 92 return nil 93 } 94 95 func isValidCred(haystack Set, needle string) bool { 96 for name := range haystack { 97 if name == needle { 98 return true 99 } 100 } 101 return false 102 } 103 104 // Resolve looks up the credentials as described in Source, then copies 105 // the resulting value into the Value field of each credential strategy. 106 // 107 // The typical workflow for working with a credential set is: 108 // 109 // - Load the set 110 // - Validate the credentials against a spec 111 // - Resolve the credentials 112 // - Expand them into bundle values 113 func (c *CredentialSet) Resolve() (Set, error) { 114 l := len(c.Credentials) 115 res := make(map[string]string, l) 116 for i := 0; i < l; i++ { 117 cred := c.Credentials[i] 118 src := cred.Source 119 // Precedence is Command, Path, EnvVar, Value 120 switch { 121 case src.Command != "": 122 data, err := execCmd(src.Command) 123 if err != nil { 124 return res, err 125 } 126 cred.Value = string(data) 127 case src.Path != "": 128 data, err := ioutil.ReadFile(os.ExpandEnv(src.Path)) 129 if err != nil { 130 return res, fmt.Errorf("credential %q: %s", c.Credentials[i].Name, err) 131 } 132 cred.Value = string(data) 133 case src.EnvVar != "": 134 var ok bool 135 cred.Value, ok = os.LookupEnv(src.EnvVar) 136 if ok { 137 break 138 } 139 fallthrough 140 default: 141 cred.Value = src.Value 142 } 143 res[c.Credentials[i].Name] = cred.Value 144 } 145 return res, nil 146 } 147 148 func execCmd(cmd string) ([]byte, error) { 149 parts := strings.Split(cmd, " ") 150 c := parts[0] 151 args := parts[1:] 152 run := exec.Command(c, args...) 153 154 return run.CombinedOutput() 155 } 156 157 // CredentialStrategy represents a source credential and the destination to which it should be sent. 158 type CredentialStrategy struct { 159 // Name is the name of the credential. 160 // Name is used to match a credential strategy to a bundle's credential. 161 Name string `json:"name" yaml:"name"` 162 // Source is the location of the credential. 163 // During resolution, the source will be loaded, and the result temporarily placed 164 // into Value. 165 Source Source `json:"source,omitempty" yaml:"source,omitempty"` 166 // Value holds the credential value. 167 // When a credential is loaded, it is loaded into this field. In all 168 // other cases, it is empty. This field is omitted during serialization. 169 Value string `json:"-" yaml:"-"` 170 } 171 172 // Source represents a strategy for loading a credential from local host. 173 type Source struct { 174 Path string `json:"path,omitempty" yaml:"path,omitempty"` 175 Command string `json:"command,omitempty" yaml:"command,omitempty"` 176 Value string `json:"value,omitempty" yaml:"value,omitempty"` 177 EnvVar string `json:"env,omitempty" yaml:"env,omitempty"` 178 } 179 180 // Destination reprents a strategy for injecting a credential into an image. 181 type Destination struct { 182 Value string `json:"value,omitempty" yaml:"value,omitempty"` 183 }