Newer
Older
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/registry"
"github.com/openfaas/faas/gateway/metrics"
"github.com/openfaas/faas/gateway/requests"
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()
request := requests.CreateFunctionRequest{}
err := json.Unmarshal(body, &request)
if err != nil {
log.Println("Error parsing request:", err)
w.WriteHeader(http.StatusBadRequest)
return
}
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, 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 {
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(c *client.Client, request *requests.CreateFunctionRequest, maxRestarts uint64, restartDelay time.Duration) (swarm.ServiceSpec, error) {
linuxOnlyConstraints := []string{"node.platform.os == linux"}
if request.Constraints != nil && len(request.Constraints) > 0 {
constraints = request.Constraints
} else {
constraints = linuxOnlyConstraints
}
labels := map[string]string{
"com.openfaas.function": request.Service,
"function": "true", // backwards-compatible
}
{
Target: request.Network,
},
secrets, err := makeSecretsArray(c, request.Secrets)
if err != nil {
return swarm.ServiceSpec{}, err
}
Annotations: swarm.Annotations{
Name: request.Service,
Labels: labels,
},
TaskTemplate: swarm.TaskSpec{
RestartPolicy: &swarm.RestartPolicy{
MaxAttempts: &maxRestarts,
Condition: swarm.RestartPolicyConditionAny,
Delay: &restartDelay,
Image: request.Image,
Labels: labels,
Secrets: secrets,
Mode: swarm.ServiceMode{
Replicated: &swarm.ReplicatedService{
Replicas: getMinReplicas(request),
},
},
// 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
}
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
}
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
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
}
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
func ParseMemory(value string) (int64, error) {
return units.RAMInBytes(value)
}
func buildResources(request *requests.CreateFunctionRequest) *swarm.ResourceRequirements {
var resources *swarm.ResourceRequirements
if request.Requests != nil || request.Limits != nil {
resources = &swarm.ResourceRequirements{}
if request.Limits != nil {
memoryBytes, err := ParseMemory(request.Limits.Memory)
if err != nil {
log.Printf("Error parsing memory limit: %T", err)
}
resources.Limits = &swarm.Resources{
MemoryBytes: memoryBytes,
}
}
if request.Requests != nil {
memoryBytes, err := ParseMemory(request.Requests.Memory)
if err != nil {
log.Printf("Error parsing memory request: %T", err)
}
resources.Reservations = &swarm.Resources{
MemoryBytes: memoryBytes,
}
}
}
return resources
}
func getMinReplicas(request *requests.CreateFunctionRequest) *uint64 {
replicas := uint64(1)
if request.Labels != nil {
if val, exists := (*request.Labels)["com.openfaas.scale.min"]; exists {
value, err := strconv.Atoi(val)
if err != nil {
log.Println(err)
}
replicas = uint64(value)
}
}
return &replicas
}
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
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
}