mirror of
https://github.com/openfaas/faas.git
synced 2025-06-10 17:26:47 +00:00
This commit implements an update handler for Docker Swarm, it queries the current spec, updates values in-situ before calling ServiceUpdate. The UpdateConfig FailureAction is set to rollback, so in the event of supplying values to the update that would result in the service failing then the update will be rolled back. The UpdateConfig Parallelism param is set to an explicit value of 1 which will result in functions being updated 1 by 1 rather than all at once. It also moves the restartDelay declaration out of the create and update handlers and into the main server function alongside maxRestarts. And finally this commit uses the PUT HTTP verb for updates rather than the non-HTTP UPDATE verb which was being used initially (also adding it to the Swagger definition). Signed-off-by: John McCabe <john@johnmccabe.net>
163 lines
4.3 KiB
Go
163 lines
4.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/alexellis/faas/gateway/metrics"
|
|
"github.com/alexellis/faas/gateway/requests"
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/swarm"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/registry"
|
|
)
|
|
|
|
var linuxOnlyConstraints = []string{"node.platform.os == linux"}
|
|
|
|
// MakeNewFunctionHandler creates a new function (service) inside the swarm network.
|
|
func MakeNewFunctionHandler(metricsOptions metrics.MetricOptions, c *client.Client, maxRestarts uint64, restartDelay time.Duration) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
body, _ := ioutil.ReadAll(r.Body)
|
|
|
|
request := requests.CreateFunctionRequest{}
|
|
err := json.Unmarshal(body, &request)
|
|
if err != nil {
|
|
log.Println("Error parsing request:", err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
fmt.Println(request)
|
|
|
|
// TODO: review why this was here... debugging?
|
|
// w.WriteHeader(http.StatusNotImplemented)
|
|
|
|
options := types.ServiceCreateOptions{}
|
|
if len(request.RegistryAuth) > 0 {
|
|
auth, err := BuildEncodedAuthConfig(request.RegistryAuth, request.Image)
|
|
if err != nil {
|
|
log.Println("Error building registry auth configuration:", err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte("Invalid registry auth"))
|
|
return
|
|
}
|
|
options.EncodedRegistryAuth = auth
|
|
}
|
|
spec := makeSpec(&request, maxRestarts, restartDelay)
|
|
|
|
response, err := c.ServiceCreate(context.Background(), spec, options)
|
|
if err != nil {
|
|
log.Println("Error creating service:", err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte("Deployment error: " + err.Error()))
|
|
return
|
|
}
|
|
log.Println(response.ID, response.Warnings)
|
|
}
|
|
}
|
|
|
|
func makeSpec(request *requests.CreateFunctionRequest, maxRestarts uint64, restartDelay time.Duration) swarm.ServiceSpec {
|
|
constraints := []string{}
|
|
if request.Constraints != nil && len(request.Constraints) > 0 {
|
|
constraints = request.Constraints
|
|
} else {
|
|
constraints = linuxOnlyConstraints
|
|
}
|
|
|
|
nets := []swarm.NetworkAttachmentConfig{
|
|
{Target: request.Network},
|
|
}
|
|
|
|
spec := swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
RestartPolicy: &swarm.RestartPolicy{
|
|
MaxAttempts: &maxRestarts,
|
|
Condition: swarm.RestartPolicyConditionAny,
|
|
Delay: &restartDelay,
|
|
},
|
|
ContainerSpec: swarm.ContainerSpec{
|
|
Image: request.Image,
|
|
Labels: map[string]string{"function": "true"},
|
|
},
|
|
Networks: nets,
|
|
Placement: &swarm.Placement{
|
|
Constraints: constraints,
|
|
},
|
|
},
|
|
Annotations: swarm.Annotations{
|
|
Name: request.Service,
|
|
},
|
|
}
|
|
|
|
// TODO: request.EnvProcess should only be set if it's not nil, otherwise we override anything in the Docker image already
|
|
env := buildEnv(request.EnvProcess, request.EnvVars)
|
|
|
|
if len(env) > 0 {
|
|
spec.TaskTemplate.ContainerSpec.Env = env
|
|
}
|
|
|
|
return spec
|
|
}
|
|
|
|
func buildEnv(envProcess string, envVars map[string]string) []string {
|
|
var env []string
|
|
if len(envProcess) > 0 {
|
|
env = append(env, fmt.Sprintf("fprocess=%s", envProcess))
|
|
}
|
|
for k, v := range envVars {
|
|
env = append(env, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
return env
|
|
}
|
|
|
|
// BuildEncodedAuthConfig for private registry
|
|
func BuildEncodedAuthConfig(basicAuthB64 string, dockerImage string) (string, error) {
|
|
// extract registry server address
|
|
distributionRef, err := reference.ParseNormalizedNamed(dockerImage)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// extract registry user & password
|
|
user, password, err := userPasswordFromBasicAuth(basicAuthB64)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// build encoded registry auth config
|
|
buf, err := json.Marshal(types.AuthConfig{
|
|
Username: user,
|
|
Password: password,
|
|
ServerAddress: repoInfo.Index.Name,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return base64.URLEncoding.EncodeToString(buf), nil
|
|
}
|
|
|
|
func userPasswordFromBasicAuth(basicAuthB64 string) (string, string, error) {
|
|
c, err := base64.StdEncoding.DecodeString(basicAuthB64)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
cs := string(c)
|
|
s := strings.IndexByte(cs, ':')
|
|
if s < 0 {
|
|
return "", "", errors.New("Invalid basic auth")
|
|
}
|
|
return cs[:s], cs[s+1:], nil
|
|
}
|