Skip to content
This repository has been archived by the owner on Dec 16, 2020. It is now read-only.

Commit

Permalink
Add annotation support
Browse files Browse the repository at this point in the history
Store stack.yml annotation meta data in deploy, update and read
handlers

Relates to openfaas/faas#682

Signed-off-by: Edward Wilde <[email protected]>
  • Loading branch information
ewilde authored and alexellis committed Aug 1, 2018
1 parent 78fe2cb commit cd4ff36
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
faas-swarm
.idea
2 changes: 1 addition & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
go-tests = true
unused-packages = true

[[constraint]]
name = "github.com/moby/moby"
revision = "4a804016ab8b9fd55a45cff9687a1de12dee5eb7"

[[constraint]]
name = "github.com/docker/go-units"
version = "0.3.2"
Expand All @@ -26,3 +22,7 @@
[[override]]
name = "github.com/docker/distribution"
revision = "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c"

[[override]]
name = "github.com/moby/moby"
revision = "4a804016ab8b9fd55a45cff9687a1de12dee5eb7"
55 changes: 43 additions & 12 deletions handlers/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"github.com/openfaas/faas/gateway/requests"
)

const annotationLabelPrefix = "com.openfaas.annotations."

var linuxOnlyConstraints = []string{"node.platform.os == linux"}

// DeployHandler creates a new function (service) inside the swarm network.
Expand Down Expand Up @@ -72,7 +74,15 @@ func DeployHandler(c *client.Client, maxRestarts uint64, restartDelay time.Durat
}
}

spec := makeSpec(&request, maxRestarts, restartDelay, secrets)
spec, err := makeSpec(&request, maxRestarts, restartDelay, secrets)
if err != nil {

log.Printf("Error creating specification: %s\n", err)

w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Deployment error: " + err.Error()))
return
}

response, err := c.ServiceCreate(context.Background(), spec, options)
if err != nil {
Expand Down Expand Up @@ -109,7 +119,7 @@ func lookupNetwork(c *client.Client) (string, error) {
return "", nil
}

func makeSpec(request *requests.CreateFunctionRequest, maxRestarts uint64, restartDelay time.Duration, secrets []*swarm.SecretReference) swarm.ServiceSpec {
func makeSpec(request *requests.CreateFunctionRequest, maxRestarts uint64, restartDelay time.Duration, secrets []*swarm.SecretReference) (swarm.ServiceSpec, error) {
constraints := []string{}

if request.Constraints != nil && len(request.Constraints) > 0 {
Expand All @@ -118,15 +128,10 @@ func makeSpec(request *requests.CreateFunctionRequest, maxRestarts uint64, resta
constraints = linuxOnlyConstraints
}

labels := map[string]string{
"com.openfaas.function": request.Service,
"function": "true", // backwards-compatible
}

if request.Labels != nil {
for k, v := range *request.Labels {
labels[k] = v
}
labels, err := buildLabels(request)
if err != nil {
nilSpec := swarm.ServiceSpec{}
return nilSpec, err
}

resources := buildResources(request)
Expand Down Expand Up @@ -183,7 +188,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 {
Expand Down Expand Up @@ -343,3 +348,29 @@ func getMinReplicas(request *requests.CreateFunctionRequest) *uint64 {
}
return &replicas
}

func buildLabels(request *requests.CreateFunctionRequest) (map[string]string, error) {
labels := map[string]string{
"com.openfaas.function": request.Service,
"function": "true", // backwards-compatible
}

if request.Labels != nil {
for k, v := range *request.Labels {
labels[k] = v
}
}

if request.Annotations != nil {
for k,v := range *request.Annotations {
key := fmt.Sprintf("%s%s", annotationLabelPrefix, k)
if _, ok := labels[key]; !ok {
labels[key] = v
} else {
return nil, errors.New(fmt.Sprintf("Keys %s can not be used as a labels as is clashes with annotations", k))
}
}
}

return labels, nil
}
85 changes: 85 additions & 0 deletions handlers/deploy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package handlers

import (
"testing"
"github.com/openfaas/faas/gateway/requests"
"fmt"
)

func Test_BuildLabels_Defaults(t *testing.T) {
request := &requests.CreateFunctionRequest {}
val, err := buildLabels(request)

if err != nil {
t.Fatalf("want: no error got: %v", err)
}

if len(val) != 2 {
t.Errorf("want: %d entries in label map got: %d", 2, len(val))
}

if _, ok := val["com.openfaas.function"]; !ok {
t.Errorf("want: '%s' entry in label map got: key not found", "com.openfaas.function")
}

if _, ok := val["function"]; !ok {
t.Errorf("want: '%s' entry in label map got: key not found", "function")
}
}

func Test_BuildLabels_WithAnnotations(t *testing.T) {
request := &requests.CreateFunctionRequest {
Labels : &map[string]string{"function_name": "echo"},
Annotations: &map[string]string{"current-time": "Wed 25 Jul 06:41:43 BST 2018"},
}

val, err := buildLabels(request)

if err != nil {
t.Fatalf("want: no error got: %v", err)
}

if len(val) != 4 {
t.Errorf("want: %d entries in combined label annotation map got: %d", 4, len(val))
}

if _, ok := val[fmt.Sprintf("%scurrent-time", annotationLabelPrefix)]; !ok {
t.Errorf("want: '%s' entry in combined label annotation map got: key not found", "annotation: current-time")
}
}

func Test_BuildLabels_NoAnnotations(t *testing.T) {
request := &requests.CreateFunctionRequest {
Labels : &map[string]string{"function_name": "echo"},
}

val, err := buildLabels(request)

if err != nil {
t.Fatalf("want: no error got: %v", err)
}

if len(val) != 3 {
t.Errorf("want: %d entries in combined label annotation map got: %d", 3, len(val))
}

if _, ok := val["function_name"]; !ok {
t.Errorf("want: '%s' entry in combined label annotation map got: key not found", "function_name")
}
}

func Test_BuildLabels_KeyClash(t *testing.T) {
request := &requests.CreateFunctionRequest{
Labels: & map[string]string{
"function_name": "echo",
fmt.Sprintf("%scurrent-time", annotationLabelPrefix): "foo",
},
Annotations: &map[string]string{"current-time": "Wed 25 Jul 06:41:43 BST 2018"},
}

_, err := buildLabels(request)

if err == nil {
t.Fatal("want: an error got: nil")
}
}
23 changes: 22 additions & 1 deletion handlers/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func readServices(c client.ServiceAPIClient) ([]requests.Function, error) {
envProcess := getEnvProcess(service.Spec.TaskTemplate.ContainerSpec.Env)

// Required (copy by value)
labels := service.Spec.Annotations.Labels
labels, annotations := buildLabelsAndAnnotations(service.Spec.Labels)

f := requests.Function{
Name: service.Spec.Name,
Expand All @@ -64,6 +64,7 @@ func readServices(c client.ServiceAPIClient) ([]requests.Function, error) {
Replicas: *service.Spec.Mode.Replicated.Replicas,
EnvProcess: envProcess,
Labels: &labels,
Annotations: &annotations,
}

functions = append(functions, f)
Expand All @@ -83,3 +84,23 @@ func getEnvProcess(envVars []string) string {

return value
}

func buildLabelsAndAnnotations(dockerLabels map[string]string) (labels map[string]string, annotations map[string]string) {
for k, v := range dockerLabels {
if strings.HasPrefix(k, annotationLabelPrefix) {
if annotations == nil {
annotations = make(map[string]string)
}

annotations[k] = v
} else {
if labels == nil {
labels = make(map[string]string)
}

labels[k] = v
}
}

return labels, annotations
}
63 changes: 63 additions & 0 deletions handlers/reader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package handlers

import (
"fmt"
"testing"
)

func Test_BuildLabelsAndAnnotationsFromServiceSpec_NoLabels(t *testing.T) {
container := make(map[string]string)

labels, annotation := buildLabelsAndAnnotations(container)

if len(labels) != 0 {
t.Errorf("want: %d entries labels got: %d", 0, len(labels))
}

if len(annotation) != 0 {
t.Errorf("want: %d entries annotations got: %d", 0, len(annotation))
}
}

func Test_BuildLabelsAndAnnotationsFromServiceSpec_Labels(t *testing.T) {
container := map[string]string{
"foo": "baa",
"fizz": "buzz",
}

labels, annotation := buildLabelsAndAnnotations(container)

if len(labels) != 2 {
t.Errorf("want: %d labels got: %d", 2, len(labels))
}

if len(annotation) != 0 {
t.Errorf("want: %d annotations got: %d", 0, len(annotation))
}

if _, ok := labels["fizz"]; !ok {
t.Errorf("want: '%s' entry in label map got: key not found", "fizz")
}
}

func Test_BuildLabelsAndAnnotationsFromServiceSpec_Annotations(t *testing.T) {
container := map[string]string{
"foo": "baa",
"fizz": "buzz",
fmt.Sprintf("%scurrent-time", annotationLabelPrefix): "Wed 25 Jul 07:10:34 BST 2018",
}

labels, annotation := buildLabelsAndAnnotations(container)

if len(labels) != 2 {
t.Errorf("want: %d labels got: %d", 2, len(labels))
}

if len(annotation) != 1 {
t.Errorf("want: %d annotation got: %d", 1, len(annotation))
}

if _, ok := annotation[fmt.Sprintf("%scurrent-time", annotationLabelPrefix)]; !ok {
t.Errorf("want: '%s' entry in annotation map got: key not found", "current-time")
}
}
28 changes: 16 additions & 12 deletions handlers/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,12 @@ func UpdateHandler(c *client.Client, maxRestarts uint64, restartDelay time.Durat
}
}

updateSpec(&request, &service.Spec, maxRestarts, restartDelay, secrets)
if err := updateSpec(&request, &service.Spec, maxRestarts, restartDelay, secrets); err != nil {
log.Println("Error updating service spec:", err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Update spc error: " + err.Error()))
return
}

updateOpts := types.ServiceUpdateOptions{}
updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec
Expand Down Expand Up @@ -96,7 +101,7 @@ func UpdateHandler(c *client.Client, maxRestarts uint64, restartDelay time.Durat
}
}

func updateSpec(request *requests.CreateFunctionRequest, spec *swarm.ServiceSpec, maxRestarts uint64, restartDelay time.Duration, secrets []*swarm.SecretReference) {
func updateSpec(request *requests.CreateFunctionRequest, spec *swarm.ServiceSpec, maxRestarts uint64, restartDelay time.Duration, secrets []*swarm.SecretReference) error {

constraints := []string{}
if request.Constraints != nil && len(request.Constraints) > 0 {
Expand All @@ -109,19 +114,16 @@ func updateSpec(request *requests.CreateFunctionRequest, spec *swarm.ServiceSpec
spec.TaskTemplate.RestartPolicy.Condition = swarm.RestartPolicyConditionAny
spec.TaskTemplate.RestartPolicy.Delay = &restartDelay
spec.TaskTemplate.ContainerSpec.Image = request.Image
spec.TaskTemplate.ContainerSpec.Labels = map[string]string{
"function": "true",
"com.openfaas.function": request.Service,
"com.openfaas.uid": fmt.Sprintf("%d", time.Now().Nanosecond()),
}

if request.Labels != nil {
for k, v := range *request.Labels {
spec.TaskTemplate.ContainerSpec.Labels[k] = v
spec.Annotations.Labels[k] = v
}
labels, err := buildLabels(request)
if err != nil {
return err
}

spec.Annotations.Labels = labels
spec.TaskTemplate.ContainerSpec.Labels = labels
spec.TaskTemplate.ContainerSpec.Labels["com.openfaas.uid"] = fmt.Sprintf("%d", time.Now().Nanosecond())

spec.TaskTemplate.Networks = []swarm.NetworkAttachmentConfig{
{
Target: request.Network,
Expand Down Expand Up @@ -167,6 +169,8 @@ func updateSpec(request *requests.CreateFunctionRequest, spec *swarm.ServiceSpec
if spec.Mode.Replicated != nil {
spec.Mode.Replicated.Replicas = getMinReplicas(request)
}

return nil
}

// removeMounts returns a mount.Mount slice with any mounts matching target removed
Expand Down

0 comments on commit cd4ff36

Please sign in to comment.