1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Add support for swarm mode templating

Wire templating support of swarmkit for the engine, in order to be used
through services.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2016-11-09 23:28:06 +01:00
parent 6e885540a2
commit 6212ea669b
No known key found for this signature in database
GPG key ID: 083CC6FD6EB699A3
6 changed files with 266 additions and 9 deletions

View file

@ -20,6 +20,7 @@ import (
"github.com/docker/swarmkit/agent/exec" "github.com/docker/swarmkit/agent/exec"
"github.com/docker/swarmkit/api" "github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/protobuf/ptypes" "github.com/docker/swarmkit/protobuf/ptypes"
"github.com/docker/swarmkit/template"
) )
const ( const (
@ -68,6 +69,17 @@ func (c *containerConfig) setTask(t *api.Task) error {
} }
c.task = t c.task = t
if t.Spec.GetContainer() != nil {
preparedSpec, err := template.ExpandContainerSpec(t)
if err != nil {
return err
}
c.task.Spec.Runtime = &api.TaskSpec_Container{
Container: preparedSpec,
}
}
return nil return nil
} }

View file

@ -118,6 +118,21 @@ func (s *DockerSwarmSuite) TestSwarmNodeListHostname(c *check.C) {
c.Assert(strings.Split(out, "\n")[0], checker.Contains, "HOSTNAME") c.Assert(strings.Split(out, "\n")[0], checker.Contains, "HOSTNAME")
} }
func (s *DockerSwarmSuite) TestSwarmServiceTemplatingHostname(c *check.C) {
d := s.AddDaemon(c, true, true)
out, err := d.Cmd("service", "create", "--name", "test", "--hostname", "{{.Service.Name}}-{{.Task.Slot}}", "busybox", "top")
c.Assert(err, checker.IsNil, check.Commentf(out))
// make sure task has been deployed.
waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 1)
containers := d.activeContainers()
out, err = d.Cmd("inspect", "--type", "container", "--format", "{{.Config.Hostname}}", containers[0])
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.Split(out, "\n")[0], checker.Equals, "test-1", check.Commentf("hostname with templating invalid"))
}
// Test case for #24270 // Test case for #24270
func (s *DockerSwarmSuite) TestSwarmServiceListFilter(c *check.C) { func (s *DockerSwarmSuite) TestSwarmServiceListFilter(c *check.C) {
d := s.AddDaemon(c, true, true) d := s.AddDaemon(c, true, true)
@ -343,17 +358,17 @@ func (s *DockerSwarmSuite) TestSwarmContainerAutoStart(c *check.C) {
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
out, err = d.Cmd("run", "-id", "--restart=always", "--net=foo", "--name=test", "busybox", "top") out, err = d.Cmd("run", "-id", "--restart=always", "--net=foo", "--name=test", "busybox", "top")
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
out, err = d.Cmd("ps", "-q") out, err = d.Cmd("ps", "-q")
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
d.Restart() d.Restart()
out, err = d.Cmd("ps", "-q") out, err = d.Cmd("ps", "-q")
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
} }
@ -361,20 +376,20 @@ func (s *DockerSwarmSuite) TestSwarmContainerEndpointOptions(c *check.C) {
d := s.AddDaemon(c, true, true) d := s.AddDaemon(c, true, true)
out, err := d.Cmd("network", "create", "--attachable", "-d", "overlay", "foo") out, err := d.Cmd("network", "create", "--attachable", "-d", "overlay", "foo")
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
_, err = d.Cmd("run", "-d", "--net=foo", "--name=first", "--net-alias=first-alias", "busybox", "top") _, err = d.Cmd("run", "-d", "--net=foo", "--name=first", "--net-alias=first-alias", "busybox", "top")
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil, check.Commentf(out))
_, err = d.Cmd("run", "-d", "--net=foo", "--name=second", "busybox", "top") _, err = d.Cmd("run", "-d", "--net=foo", "--name=second", "busybox", "top")
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil, check.Commentf(out))
// ping first container and its alias // ping first container and its alias
_, err = d.Cmd("exec", "second", "ping", "-c", "1", "first") _, err = d.Cmd("exec", "second", "ping", "-c", "1", "first")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil, check.Commentf(out))
_, err = d.Cmd("exec", "second", "ping", "-c", "1", "first-alias") _, err = d.Cmd("exec", "second", "ping", "-c", "1", "first-alias")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil, check.Commentf(out))
} }
func (s *DockerSwarmSuite) TestSwarmContainerAttachByNetworkId(c *check.C) { func (s *DockerSwarmSuite) TestSwarmContainerAttachByNetworkId(c *check.C) {

View file

@ -14,6 +14,7 @@ import (
"github.com/docker/swarmkit/manager/constraint" "github.com/docker/swarmkit/manager/constraint"
"github.com/docker/swarmkit/manager/state/store" "github.com/docker/swarmkit/manager/state/store"
"github.com/docker/swarmkit/protobuf/ptypes" "github.com/docker/swarmkit/protobuf/ptypes"
"github.com/docker/swarmkit/template"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@ -168,7 +169,28 @@ func validateTask(taskSpec api.TaskSpec) error {
return grpc.Errorf(codes.Unimplemented, "RuntimeSpec: unimplemented runtime in service spec") return grpc.Errorf(codes.Unimplemented, "RuntimeSpec: unimplemented runtime in service spec")
} }
if err := validateContainerSpec(taskSpec.GetContainer()); err != nil { // Building a empty/dummy Task to validate the templating and
// the resulting container spec as well. This is a *best effort*
// validation.
preparedSpec, err := template.ExpandContainerSpec(&api.Task{
Spec: taskSpec,
ServiceID: "serviceid",
Slot: 1,
NodeID: "nodeid",
Networks: []*api.NetworkAttachment{},
Annotations: api.Annotations{
Name: "taskname",
},
ServiceAnnotations: api.Annotations{
Name: "servicename",
},
Endpoint: &api.Endpoint{},
LogDriver: taskSpec.LogDriver,
})
if err != nil {
return grpc.Errorf(codes.InvalidArgument, err.Error())
}
if err := validateContainerSpec(preparedSpec); err != nil {
return err return err
} }

72
vendor/github.com/docker/swarmkit/template/context.go generated vendored Normal file
View file

@ -0,0 +1,72 @@
package template
import (
"bytes"
"fmt"
"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/api/naming"
)
// Context defines the strict set of values that can be injected into a
// template expression in SwarmKit data structure.
type Context struct {
Service struct {
ID string
Name string
Labels map[string]string
}
Node struct {
ID string
}
Task struct {
ID string
Name string
Slot string
// NOTE(stevvooe): Why no labels here? Tasks don't actually have labels
// (from a user perspective). The labels are part of the container! If
// one wants to use labels for templating, use service labels!
}
}
// NewContextFromTask returns a new template context from the data available in
// task. The provided context can then be used to populate runtime values in a
// ContainerSpec.
func NewContextFromTask(t *api.Task) (ctx Context) {
ctx.Service.ID = t.ServiceID
ctx.Service.Name = t.ServiceAnnotations.Name
ctx.Service.Labels = t.ServiceAnnotations.Labels
ctx.Node.ID = t.NodeID
ctx.Task.ID = t.ID
ctx.Task.Name = naming.Task(t)
if t.Slot != 0 {
ctx.Task.Slot = fmt.Sprint(t.Slot)
} else {
// fall back to node id for slot when there is no slot
ctx.Task.Slot = t.NodeID
}
return
}
// Expand treats the string s as a template and populates it with values from
// the context.
func (ctx *Context) Expand(s string) (string, error) {
tmpl, err := newTemplate(s)
if err != nil {
return s, err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, ctx); err != nil {
return s, err
}
return buf.String(), nil
}

118
vendor/github.com/docker/swarmkit/template/expand.go generated vendored Normal file
View file

@ -0,0 +1,118 @@
package template
import (
"fmt"
"strings"
"github.com/docker/swarmkit/api"
"github.com/pkg/errors"
)
// ExpandContainerSpec expands templated fields in the runtime using the task
// state. Templating is all evaluated on the agent-side, before execution.
//
// Note that these are projected only on runtime values, since active task
// values are typically manipulated in the manager.
func ExpandContainerSpec(t *api.Task) (*api.ContainerSpec, error) {
container := t.Spec.GetContainer()
if container == nil {
return nil, errors.Errorf("task missing ContainerSpec to expand")
}
container = container.Copy()
ctx := NewContextFromTask(t)
var err error
container.Env, err = expandEnv(ctx, container.Env)
if err != nil {
return container, errors.Wrap(err, "expanding env failed")
}
// For now, we only allow templating of string-based mount fields
container.Mounts, err = expandMounts(ctx, container.Mounts)
if err != nil {
return container, errors.Wrap(err, "expanding mounts failed")
}
container.Hostname, err = ctx.Expand(container.Hostname)
return container, errors.Wrap(err, "expanding hostname failed")
}
func expandMounts(ctx Context, mounts []api.Mount) ([]api.Mount, error) {
if len(mounts) == 0 {
return mounts, nil
}
expanded := make([]api.Mount, len(mounts))
for i, mount := range mounts {
var err error
mount.Source, err = ctx.Expand(mount.Source)
if err != nil {
return mounts, errors.Wrapf(err, "expanding mount source %q", mount.Source)
}
mount.Target, err = ctx.Expand(mount.Target)
if err != nil {
return mounts, errors.Wrapf(err, "expanding mount target %q", mount.Target)
}
if mount.VolumeOptions != nil {
mount.VolumeOptions.Labels, err = expandMap(ctx, mount.VolumeOptions.Labels)
if err != nil {
return mounts, errors.Wrap(err, "expanding volume labels")
}
if mount.VolumeOptions.DriverConfig != nil {
mount.VolumeOptions.DriverConfig.Options, err = expandMap(ctx, mount.VolumeOptions.DriverConfig.Options)
if err != nil {
return mounts, errors.Wrap(err, "expanding volume driver config")
}
}
}
expanded[i] = mount
}
return expanded, nil
}
func expandMap(ctx Context, m map[string]string) (map[string]string, error) {
var (
n = make(map[string]string, len(m))
err error
)
for k, v := range m {
v, err = ctx.Expand(v)
if err != nil {
return m, errors.Wrapf(err, "expanding map entry %q=%q", k, v)
}
n[k] = v
}
return n, nil
}
func expandEnv(ctx Context, values []string) ([]string, error) {
var result []string
for _, value := range values {
var (
parts = strings.SplitN(value, "=", 2)
entry = parts[0]
)
if len(parts) > 1 {
expanded, err := ctx.Expand(parts[1])
if err != nil {
return values, errors.Wrapf(err, "expanding env %q", value)
}
entry = fmt.Sprintf("%s=%s", entry, expanded)
}
result = append(result, entry)
}
return result, nil
}

18
vendor/github.com/docker/swarmkit/template/template.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
package template
import (
"strings"
"text/template"
)
// funcMap defines functions for our template system.
var funcMap = template.FuncMap{
"join": func(s ...string) string {
// first arg is sep, remaining args are strings to join
return strings.Join(s[1:], s[0])
},
}
func newTemplate(s string) (*template.Template, error) {
return template.New("expansion").Option("missingkey=error").Funcs(funcMap).Parse(s)
}