Skip to content

Commit

Permalink
Support environment vars in Swarm plugins services
Browse files Browse the repository at this point in the history
Allow specifying environment variables when installing an engine plugin
as a Swarm service. Invalid environment variable entries (without an
equals (`=`) char) will be ignored.

Signed-off-by: Sune Keller <absukl@almbrand.dk>
  • Loading branch information
sirlatrom committed Apr 7, 2019
1 parent 0ac8cbf commit fca5ee3
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 36 deletions.
110 changes: 76 additions & 34 deletions api/types/swarm/runtime/plugin.pb.go

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

1 change: 1 addition & 0 deletions api/types/swarm/runtime/plugin.proto
Expand Up @@ -9,6 +9,7 @@ message PluginSpec {
string remote = 2;
repeated PluginPrivilege privileges = 3;
bool disabled = 4;
repeated string env = 5;
}

// PluginPrivilege describes a permission the user has to accept
Expand Down
2 changes: 1 addition & 1 deletion daemon/cluster/controllers/plugin/controller.go
Expand Up @@ -122,7 +122,7 @@ func (p *Controller) Prepare(ctx context.Context) (err error) {
return p.backend.Upgrade(ctx, remote, p.spec.Name, nil, &authConfig, privs, ioutil.Discard)
}

if err := p.backend.Pull(ctx, remote, p.spec.Name, nil, &authConfig, privs, ioutil.Discard, plugin.WithSwarmService(p.serviceID)); err != nil {
if err := p.backend.Pull(ctx, remote, p.spec.Name, nil, &authConfig, privs, ioutil.Discard, plugin.WithSwarmService(p.serviceID), plugin.WithEnv(p.spec.Env)); err != nil {
return err
}
pl, err = p.backend.Get(p.spec.Name)
Expand Down
24 changes: 24 additions & 0 deletions integration/service/plugin_test.go
Expand Up @@ -6,9 +6,11 @@ import (
"io/ioutil"
"os"
"path"
"strings"
"testing"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/swarm/runtime"
"github.com/docker/docker/integration/internal/swarm"
Expand Down Expand Up @@ -68,7 +70,25 @@ func TestServicePlugin(t *testing.T) {
poll.WaitOn(t, d2.PluginIsRunning(t, name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsRunning(t, name), swarm.ServicePoll)

// test that environment variables are passed from plugin service to plugin instance
service := d1.GetService(t, id)
tasks := d1.GetServiceTasks(t, service.Spec.Annotations.Name, filters.Arg("runtime", "plugin"))
if len(tasks) == 0 {
t.Log("No tasks found for plugin service")
t.Fail()
}
plugin, _, err := d1.NewClientT(t).PluginInspectWithRaw(context.Background(), name)
assert.NilError(t, err, "Error inspecting service plugin")
found := false
for _, env := range plugin.Settings.Env {
assert.Equal(t, strings.HasPrefix(env, "baz"), false, "Environment variable entry %q is invalid and should not be present", "baz")
if strings.HasPrefix(env, "foo=") {
found = true
assert.Equal(t, env, "foo=bar")
}
}
assert.Equal(t, true, found, "Environment variable %q not found in plugin", "foo")

d1.UpdateService(t, service, makePlugin(repo2, name, nil))
poll.WaitOn(t, d1.PluginReferenceIs(t, name, repo2), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginReferenceIs(t, name, repo2), swarm.ServicePoll)
Expand Down Expand Up @@ -111,6 +131,10 @@ func makePlugin(repo, name string, constraints []string) func(*swarmtypes.Servic
s.Spec.TaskTemplate.PluginSpec = &runtime.PluginSpec{
Name: name,
Remote: repo,
Env: []string{
"baz", // invalid environment variable entries are ignored
"foo=bar", // "foo" will be the single environment variable
},
}
if constraints != nil {
s.Spec.TaskTemplate.Placement = &swarmtypes.Placement{
Expand Down
5 changes: 4 additions & 1 deletion internal/test/daemon/service.go
Expand Up @@ -56,7 +56,7 @@ func (d *Daemon) GetService(t assert.TestingT, id string) *swarm.Service {
}

// GetServiceTasks returns the swarm tasks for the specified service
func (d *Daemon) GetServiceTasks(t assert.TestingT, service string) []swarm.Task {
func (d *Daemon) GetServiceTasks(t assert.TestingT, service string, additionalFilters ...filters.KeyValuePair) []swarm.Task {
if ht, ok := t.(test.HelperT); ok {
ht.Helper()
}
Expand All @@ -66,6 +66,9 @@ func (d *Daemon) GetServiceTasks(t assert.TestingT, service string) []swarm.Task
filterArgs := filters.NewArgs()
filterArgs.Add("desired-state", "running")
filterArgs.Add("service", service)
for _, filter := range additionalFilters {
filterArgs.Add(filter.Key, filter.Value)
}

options := types.TaskListOptions{
Filters: filterArgs,
Expand Down
27 changes: 27 additions & 0 deletions plugin/defs.go
@@ -1,6 +1,8 @@
package plugin // import "github.com/docker/docker/plugin"

import (
"fmt"
"strings"
"sync"

"github.com/docker/docker/pkg/plugins"
Expand Down Expand Up @@ -42,6 +44,31 @@ func WithSwarmService(id string) CreateOpt {
}
}

// WithEnv is a CreateOpt that passes the user-provided environment variables
// to the plugin container, de-duplicating variables with the same names case
// sensitively and only appends valid key=value pairs
func WithEnv(env []string) CreateOpt {
return func(p *v2.Plugin) {
effectiveEnv := make(map[string]string)
for _, penv := range p.PluginObj.Config.Env {
if penv.Value != nil {
effectiveEnv[penv.Name] = *penv.Value
}
}
for _, line := range env {
if pair := strings.SplitN(line, "=", 2); len(pair) > 1 {
effectiveEnv[pair[0]] = pair[1]
}
}
p.PluginObj.Settings.Env = make([]string, len(effectiveEnv))
i := 0
for key, value := range effectiveEnv {
p.PluginObj.Settings.Env[i] = fmt.Sprintf("%s=%s", key, value)
i++
}
}
}

// WithSpecMounts is a SpecOpt which appends the provided mounts to the runtime spec
func WithSpecMounts(mounts []specs.Mount) SpecOpt {
return func(s *specs.Spec) {
Expand Down

0 comments on commit fca5ee3

Please sign in to comment.