Skip to content

Commit 86a6c25

Browse files
nenadilic84alexellis
authored andcommitted
Add external Docker registry authentication support via arguments/YAML
Signed-off-by: Nenad Ilic <[email protected]> Signed-off-by: Burton Rheutan <[email protected]>
1 parent 2f4db5e commit 86a6c25

File tree

21 files changed

+1505
-21
lines changed

21 files changed

+1505
-21
lines changed

commands/deploy.go

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
package commands
55

66
import (
7+
"encoding/base64"
8+
"encoding/json"
79
"fmt"
810
"io/ioutil"
11+
"log"
912
"os"
13+
"path/filepath"
1014
"strings"
1115

1216
"gopkg.in/yaml.v2"
1317

18+
"github.com/docker/docker-credential-helpers/client"
19+
homedir "github.com/mitchellh/go-homedir"
1420
"github.com/openfaas/faas-cli/proxy"
1521
"github.com/openfaas/faas-cli/stack"
1622
"github.com/spf13/cobra"
@@ -72,7 +78,7 @@ var deployCmd = &cobra.Command{
7278
[--constraint PLACEMENT_CONSTRAINT ...]
7379
[--regex "REGEX"]
7480
[--filter "WILDCARD"]
75-
[--secret "SECRET_NAME"]`,
81+
[--secret "SECRET_NAME"]`,
7682

7783
Short: "Deploy OpenFaaS functions",
7884
Long: `Deploys OpenFaaS function containers either via the supplied YAML config using
@@ -112,6 +118,12 @@ func runDeployCommand(args []string, image string, fprocess string, functionName
112118
return fmt.Errorf("cannot specify --update and --replace at the same time")
113119
}
114120

121+
dockerConfig := configFile{}
122+
err := readDockerConfig(&dockerConfig)
123+
if err != nil {
124+
log.Println("Unable to read the docker config - %v", err.Error())
125+
}
126+
115127
var services stack.Services
116128
if len(yamlFile) > 0 {
117129
parsedServices, err := stack.ParseYAMLFile(yamlFile, regex, filter)
@@ -153,6 +165,8 @@ func runDeployCommand(args []string, image string, fprocess string, functionName
153165
deployFlags.secrets = mergeSlice(function.Secrets, deployFlags.secrets)
154166
}
155167

168+
function.RegistryAuth = getRegistryAuth(&dockerConfig, function.Image)
169+
156170
fileEnvironment, err := readFiles(function.EnvironmentFile)
157171
if err != nil {
158172
return err
@@ -191,16 +205,17 @@ Error: %s`, fprocessErr.Error())
191205
Requests: function.Requests,
192206
}
193207

194-
proxy.DeployFunction(function.FProcess, services.Provider.GatewayURL, function.Name, function.Image, function.Language, deployFlags.replace, allEnvironment, services.Provider.Network, functionConstraints, deployFlags.update, deployFlags.secrets, allLabels, functionResourceRequest1)
208+
proxy.DeployFunction(function.FProcess, services.Provider.GatewayURL, function.Name, function.Image, function.RegistryAuth, function.Language, deployFlags.replace, allEnvironment, services.Provider.Network, functionConstraints, deployFlags.update, deployFlags.secrets, allLabels, functionResourceRequest1)
195209
}
196210
} else {
197211
if len(image) == 0 || len(functionName) == 0 {
198212
return fmt.Errorf("To deploy a function give --yaml/-f or a --image flag")
199213
}
200214

201215
gateway = getGatewayURL(gateway, defaultGateway, gateway, os.Getenv(openFaaSURLEnvironment))
216+
registryAuth := getRegistryAuth(&dockerConfig, image)
202217

203-
if err := deployImage(image, fprocess, functionName, deployFlags); err != nil {
218+
if err := deployImage(image, fprocess, functionName, registryAuth, deployFlags); err != nil {
204219
return err
205220
}
206221
}
@@ -213,6 +228,7 @@ func deployImage(
213228
image string,
214229
fprocess string,
215230
functionName string,
231+
registryAuth string,
216232
deployFlags DeployFlags,
217233
) error {
218234
envvars, err := parseMap(deployFlags.envvarOpts, "env")
@@ -226,7 +242,7 @@ func deployImage(
226242
}
227243

228244
functionResourceRequest1 := proxy.FunctionResourceRequest{}
229-
proxy.DeployFunction(fprocess, gateway, functionName, image, language, deployFlags.replace, envvars, network, deployFlags.constraints, deployFlags.update, deployFlags.secrets, labelMap, functionResourceRequest1)
245+
proxy.DeployFunction(fprocess, gateway, functionName, registryAuth, image, language, deployFlags.replace, envvars, network, deployFlags.constraints, deployFlags.update, deployFlags.secrets, labelMap, functionResourceRequest1)
230246

231247
return nil
232248
}
@@ -340,3 +356,91 @@ func deriveFprocess(function stack.Function) (string, error) {
340356
func languageExistsNotDockerfile(language string) bool {
341357
return len(language) > 0 && strings.ToLower(language) != "dockerfile"
342358
}
359+
360+
type authConfig struct {
361+
Auth string `json:"auth,omitempty"`
362+
}
363+
364+
type configFile struct {
365+
AuthConfigs map[string]authConfig `json:"auths"`
366+
CredentialsStore string `json:"credsStore,omitempty"`
367+
}
368+
369+
const (
370+
// docker default settings
371+
configFileName = "config.json"
372+
configFileDir = ".docker"
373+
defaultDockerRegistry = "https://index.docker.io/v1/"
374+
)
375+
376+
var (
377+
configDir = os.Getenv("DOCKER_CONFIG")
378+
)
379+
380+
func readDockerConfig(config *configFile) error {
381+
382+
if configDir == "" {
383+
home, err := homedir.Dir()
384+
if err != nil {
385+
return err
386+
}
387+
configDir = filepath.Join(home, configFileDir)
388+
}
389+
filename := filepath.Join(configDir, configFileName)
390+
391+
file, err := os.Open(filename)
392+
if err != nil {
393+
return err
394+
}
395+
defer file.Close()
396+
content, err := ioutil.ReadAll(file)
397+
if err != nil {
398+
return err
399+
}
400+
401+
err = json.Unmarshal(content, config)
402+
if err != nil {
403+
return err
404+
}
405+
406+
if config.CredentialsStore != "" {
407+
p := client.NewShellProgramFunc("docker-credential-" + config.CredentialsStore)
408+
409+
for k := range config.AuthConfigs {
410+
creds, err := client.Get(p, k)
411+
if err != nil {
412+
return err
413+
}
414+
415+
if config.AuthConfigs[k].Auth == "" {
416+
// apend base64 encoded "auth": "dGVzdDpQdXFxR3E2THZDYzhGQUwyUWtLcA==" (user:pass)
417+
registryAuth := creds.Username + ":" + creds.Secret
418+
registryAuth = base64.StdEncoding.EncodeToString([]byte(registryAuth))
419+
420+
var tmp = config.AuthConfigs[k]
421+
tmp.Auth = registryAuth
422+
config.AuthConfigs[k] = tmp
423+
}
424+
}
425+
}
426+
return nil
427+
}
428+
429+
func getRegistryAuth(config *configFile, image string) string {
430+
431+
if len(config.AuthConfigs) == 0 {
432+
return ""
433+
}
434+
435+
// image format is: <docker registry>/<user>/<image>
436+
// so we trim <user>/<image>
437+
regS := strings.Split(image, "/")
438+
registry := strings.Join(regS[:len(regS)-2], ", ")
439+
440+
if registry != "" {
441+
return config.AuthConfigs[registry].Auth
442+
} else if (registry == "") && (config.AuthConfigs[defaultDockerRegistry].Auth != "") {
443+
return config.AuthConfigs[defaultDockerRegistry].Auth
444+
}
445+
return ""
446+
}

commands/store_deploy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,5 @@ func runStoreDeploy(cmd *cobra.Command, args []string) error {
8787

8888
gateway = getGatewayURL(gateway, defaultGateway, "", os.Getenv(openFaaSURLEnvironment))
8989

90-
return deployImage(item.Image, item.Fprocess, item.Name, storeDeployFlags)
90+
return deployImage(item.Image, item.Fprocess, item.Name, "", storeDeployFlags)
9191
}

proxy/deploy.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ type FunctionResourceRequest struct {
2323
}
2424

2525
func DeployFunction(fprocess string, gateway string, functionName string, image string,
26-
language string, replace bool, envVars map[string]string, network string,
27-
constraints []string, update bool, secrets []string, labels map[string]string,
28-
functionResourceRequest1 FunctionResourceRequest) {
26+
registryAuth string, language string, replace bool, envVars map[string]string,
27+
network string, constraints []string, update bool, secrets []string,
28+
labels map[string]string, functionResourceRequest1 FunctionResourceRequest) {
2929

3030
rollingUpdateInfo := fmt.Sprintf("Function %s already exists, attempting rolling-update.", functionName)
31-
statusCode, deployOutput := Deploy(fprocess, gateway, functionName, image, language, replace, envVars, network, constraints, update, secrets, labels, functionResourceRequest1)
31+
statusCode, deployOutput := Deploy(fprocess, gateway, functionName, image, registryAuth, language, replace, envVars, network, constraints, update, secrets, labels, functionResourceRequest1)
3232

3333
if update == true && statusCode == http.StatusNotFound {
3434
// Re-run the function with update=false
35-
_, deployOutput = Deploy(fprocess, gateway, functionName, image, language, replace, envVars, network, constraints, false, secrets, labels, functionResourceRequest1)
35+
_, deployOutput = Deploy(fprocess, gateway, functionName, image, registryAuth, language, replace, envVars, network, constraints, false, secrets, labels, functionResourceRequest1)
3636
} else if statusCode == http.StatusOK {
3737
fmt.Println(rollingUpdateInfo)
3838
}
@@ -41,9 +41,9 @@ func DeployFunction(fprocess string, gateway string, functionName string, image
4141
}
4242

4343
func Deploy(fprocess string, gateway string, functionName string, image string,
44-
language string, replace bool, envVars map[string]string, network string,
45-
constraints []string, update bool, secrets []string, labels map[string]string,
46-
functionResourceRequest1 FunctionResourceRequest) (int, string) {
44+
registryAuth string, language string, replace bool, envVars map[string]string,
45+
network string, constraints []string, update bool, secrets []string,
46+
labels map[string]string, functionResourceRequest1 FunctionResourceRequest) (int, string) {
4747

4848
var deployOutput string
4949
// Need to alter Gateway to allow nil/empty string as fprocess, to avoid this repetition.
@@ -52,21 +52,26 @@ func Deploy(fprocess string, gateway string, functionName string, image string,
5252
fprocessTemplate = fprocess
5353
}
5454

55+
if (registryAuth != "") && !strings.HasPrefix(gateway, "https") {
56+
fmt.Println("WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.")
57+
}
58+
5559
gateway = strings.TrimRight(gateway, "/")
5660

5761
if replace {
5862
DeleteFunction(gateway, functionName)
5963
}
6064

6165
req := requests.CreateFunctionRequest{
62-
EnvProcess: fprocessTemplate,
63-
Image: image,
64-
Network: network,
65-
Service: functionName,
66-
EnvVars: envVars,
67-
Constraints: constraints,
68-
Secrets: secrets, // TODO: allow registry auth to be specified or read from local Docker credentials store
69-
Labels: &labels,
66+
EnvProcess: fprocessTemplate,
67+
Image: image,
68+
RegistryAuth: registryAuth,
69+
Network: network,
70+
Service: functionName,
71+
EnvVars: envVars,
72+
Constraints: constraints,
73+
Secrets: secrets,
74+
Labels: &labels,
7075
}
7176

7277
hasLimits := false

proxy/deploy_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func runDeployProxyTest(t *testing.T, deployTest deployProxyTest) {
3535
s.URL,
3636
"function",
3737
"image",
38+
"dXNlcjpwYXNzd29yZA==",
3839
"language",
3940
deployTest.replace,
4041
nil,
@@ -93,6 +94,7 @@ func Test_DeployFunction_MissingURLPrefix(t *testing.T) {
9394
url,
9495
"function",
9596
"image",
97+
"dXNlcjpwYXNzd29yZA==",
9698
"language",
9799
false,
98100
nil,

stack/schema.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ type Function struct {
2222
// Image Docker image name
2323
Image string `yaml:"image"`
2424

25+
// Docker registry Authorization
26+
RegistryAuth string `yaml:"registry_auth,omitempty"`
27+
2528
FProcess string `yaml:"fprocess"`
2629

2730
Environment map[string]string `yaml:"environment"`

vendor/github.com/docker/docker-credential-helpers/LICENSE

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/docker-credential-helpers/README.md

Lines changed: 82 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)