Add support to specify secrets in services

**What**
- During function creation, accept an array of strings defining swarm secrets
that are required for the service
- Update docs
- Add new guide on using the secrets capability
- Add new sample function to highlight using environment variables
- Update `ApiKeyProtected` sample function to utilize the new secrets
capabilities

**Why**
- This allows secrets to remain encrypted at rest instead of being unencrypted
in environment variables and yaml files.

Fixes #285

Signed-off-by: Lucas Roesler <lucas.roesler@gmail.com>
This commit is contained in:
Lucas Roesler
2017-10-13 14:01:30 +02:00
committed by Alex Ellis
parent cc103ada94
commit 0fef825fb4
10 changed files with 196 additions and 17 deletions

View File

@ -15,6 +15,7 @@ import (
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/registry"
@ -51,7 +52,13 @@ func MakeNewFunctionHandler(metricsOptions metrics.MetricOptions, c *client.Clie
options.EncodedRegistryAuth = auth
}
spec := makeSpec(&request, maxRestarts, restartDelay)
spec, err := makeSpec(c, &request, maxRestarts, restartDelay)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Deployment error: " + err.Error()))
return
}
response, err := c.ServiceCreate(context.Background(), spec, options)
if err != nil {
@ -64,7 +71,8 @@ func MakeNewFunctionHandler(metricsOptions metrics.MetricOptions, c *client.Clie
}
}
func makeSpec(request *requests.CreateFunctionRequest, maxRestarts uint64, restartDelay time.Duration) swarm.ServiceSpec {
func makeSpec(c *client.Client, request *requests.CreateFunctionRequest, maxRestarts uint64, restartDelay time.Duration) (swarm.ServiceSpec, error) {
linuxOnlyConstraints := []string{"node.platform.os == linux"}
constraints := []string{}
if request.Constraints != nil && len(request.Constraints) > 0 {
@ -92,6 +100,11 @@ func makeSpec(request *requests.CreateFunctionRequest, maxRestarts uint64, resta
},
}
secrets, err := makeSecretsArray(c, request.Secrets)
if err != nil {
return swarm.ServiceSpec{}, err
}
spec := swarm.ServiceSpec{
Annotations: swarm.Annotations{
Name: request.Service,
@ -104,8 +117,9 @@ func makeSpec(request *requests.CreateFunctionRequest, maxRestarts uint64, resta
Delay: &restartDelay,
},
ContainerSpec: swarm.ContainerSpec{
Image: request.Image,
Labels: labels,
Image: request.Image,
Labels: labels,
Secrets: secrets,
},
Networks: nets,
Resources: resources,
@ -127,7 +141,7 @@ func makeSpec(request *requests.CreateFunctionRequest, maxRestarts uint64, resta
spec.TaskTemplate.ContainerSpec.Env = env
}
return spec
return spec, nil
}
func buildEnv(envProcess string, envVars map[string]string) []string {
@ -233,3 +247,63 @@ func getMinReplicas(request *requests.CreateFunctionRequest) *uint64 {
}
return &replicas
}
func makeSecretsArray(c *client.Client, secretNames []string) ([]*swarm.SecretReference, error) {
values := []*swarm.SecretReference{}
if len(secretNames) == 0 {
return values, nil
}
requestedSecrets := make(map[string]bool)
ctx := context.Background()
// query the Swarm for the requested secret ids, these are required to complete
// the spec
args := filters.NewArgs()
for _, name := range secretNames {
args.Add("name", name)
}
secrets, err := c.SecretList(ctx, types.SecretListOptions{
Filters: args,
})
if err != nil {
return nil, err
}
// create map of matching secrets for easy lookup
foundSecrets := make(map[string]string)
for _, secret := range secrets {
foundSecrets[secret.Spec.Annotations.Name] = secret.ID
}
// mimics the simple syntax for `docker service create --secret foo`
// and the code is based on the docker cli
for _, secretName := range secretNames {
if _, exists := requestedSecrets[secretName]; exists {
return nil, fmt.Errorf("duplicate secret target for %s not allowed", secretName)
}
id, ok := foundSecrets[secretName]
if !ok {
return nil, fmt.Errorf("secret not found: %s", secretName)
}
options := &swarm.SecretReference{
File: &swarm.SecretReferenceFileTarget{
UID: "0",
GID: "0",
Mode: 0444,
Name: secretName,
},
SecretID: id,
SecretName: secretName,
}
requestedSecrets[secretName] = true
values = append(values, options)
}
return values, nil
}