Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 3e2545d

Browse files
authored
Merge pull request #715 from rumpl/feat-run-labels
Feat run labels
2 parents cbf819c + 903f3c0 commit 3e2545d

File tree

10 files changed

+164
-11
lines changed

10 files changed

+164
-11
lines changed

cmd/cnab-run/install.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ func installAction(instanceName string) error {
5151
if err != nil {
5252
return err
5353
}
54+
if err = addLabels(rendered); err != nil {
55+
return err
56+
}
5457
addAppLabels(rendered, instanceName)
58+
5559
if err := os.Chdir(app.Path); err != nil {
5660
return err
5761
}
@@ -87,6 +91,27 @@ func getBundleImageMap() (map[string]bundle.Image, error) {
8791
return result, nil
8892
}
8993

94+
func addLabels(rendered *composetypes.Config) error {
95+
args, err := ioutil.ReadFile(internal.DockerArgsPath)
96+
if err != nil {
97+
return err
98+
}
99+
var a packager.DockerAppArgs
100+
if err := json.Unmarshal(args, &a); err != nil {
101+
return err
102+
}
103+
for k, v := range a.Labels {
104+
for i, service := range rendered.Services {
105+
if service.Labels == nil {
106+
service.Labels = map[string]string{}
107+
}
108+
service.Labels[k] = v
109+
rendered.Services[i] = service
110+
}
111+
}
112+
return nil
113+
}
114+
90115
func addAppLabels(rendered *composetypes.Config, instanceName string) {
91116
for i, service := range rendered.Services {
92117
if service.Labels == nil {

e2e/commands_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,30 @@ func TestRunOnlyOne(t *testing.T) {
193193
})
194194
}
195195

196+
func TestRunWithLabels(t *testing.T) {
197+
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
198+
cmd := info.configuredCmd
199+
200+
contextPath := filepath.Join("testdata", "simple")
201+
cmd.Command = dockerCli.Command("app", "build", "--tag", "myapp", contextPath)
202+
icmd.RunCmd(cmd).Assert(t, icmd.Success)
203+
204+
cmd.Command = dockerCli.Command("app", "run", "myapp", "--name", "myapp", "--label", "label.key=labelValue")
205+
icmd.RunCmd(cmd).Assert(t, icmd.Success)
206+
207+
services := []string{
208+
"myapp_db", "myapp_web", "myapp_api",
209+
}
210+
for _, service := range services {
211+
cmd.Command = dockerCli.Command("inspect", service)
212+
icmd.RunCmd(cmd).Assert(t, icmd.Expected{
213+
ExitCode: 0,
214+
Out: `"label.key": "labelValue"`,
215+
})
216+
}
217+
})
218+
}
219+
196220
func TestDockerAppLifecycle(t *testing.T) {
197221
t.Run("withBindMounts", func(t *testing.T) {
198222
testDockerAppLifecycle(t, true)

internal/commands/image/list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func printImageIDs(dockerCli command.Cli, refs []pkg) error {
107107
}
108108
fmt.Fprintln(&buf, stringid.TruncateID(id.String()))
109109
}
110-
fmt.Fprintf(dockerCli.Out(), buf.String())
110+
fmt.Fprint(dockerCli.Out(), buf.String())
111111
return nil
112112
}
113113

internal/commands/image/list_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@ import (
1414
"github.com/docker/distribution/reference"
1515
)
1616

17-
type mockRef string
18-
19-
func (ref mockRef) String() string {
20-
return string(ref)
21-
}
22-
2317
type bundleStoreStubForListCmd struct {
2418
refMap map[reference.Reference]*bundle.Bundle
2519
// in order to keep the reference in the same order between tests

internal/commands/parameters.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package commands
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"io"
67
"os"
78
"strings"
89

910
"github.com/deislabs/cnab-go/bundle"
1011
"github.com/docker/app/internal"
12+
"github.com/docker/app/internal/packager"
1113
"github.com/docker/app/internal/store"
1214
"github.com/docker/app/types/parameters"
1315
cliopts "github.com/docker/cli/opts"
@@ -45,6 +47,27 @@ func withCommandLineParameters(overrides []string) mergeBundleOpt {
4547
}
4648
}
4749

50+
func withLabels(labels []string) mergeBundleOpt {
51+
return func(c *mergeBundleConfig) error {
52+
for _, l := range labels {
53+
if strings.HasPrefix(l, internal.Namespace) {
54+
return errors.Errorf("labels cannot start with %q", internal.Namespace)
55+
}
56+
}
57+
l := packager.DockerAppArgs{
58+
Labels: cliopts.ConvertKVStringsToMap(labels),
59+
}
60+
out, err := json.Marshal(l)
61+
if err != nil {
62+
return err
63+
}
64+
if _, ok := c.bundle.Parameters[internal.ParameterArgs]; ok {
65+
c.params[internal.ParameterArgs] = string(out)
66+
}
67+
return nil
68+
}
69+
}
70+
4871
func withSendRegistryAuth(sendRegistryAuth bool) mergeBundleOpt {
4972
return func(c *mergeBundleConfig) error {
5073
if _, ok := c.bundle.Definitions[internal.ParameterShareRegistryCredsName]; ok {

internal/commands/parameters_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package commands
22

33
import (
44
"bytes"
5+
"encoding/json"
6+
"fmt"
57
"strings"
68
"testing"
79

810
"github.com/deislabs/cnab-go/bundle"
911
"github.com/deislabs/cnab-go/bundle/definition"
1012
"github.com/deislabs/cnab-go/claim"
1113
"github.com/docker/app/internal"
14+
"github.com/docker/app/internal/packager"
1215
"github.com/docker/app/internal/store"
1316
"gotest.tools/assert"
1417
"gotest.tools/assert/cmp"
@@ -264,3 +267,41 @@ func TestMergeBundleParameters(t *testing.T) {
264267
assert.ErrorContains(t, err, "invalid value for parameter")
265268
})
266269
}
270+
271+
func TestLabels(t *testing.T) {
272+
expected := packager.DockerAppArgs{
273+
Labels: map[string]string{
274+
"label": "value",
275+
},
276+
}
277+
expectedStr, err := json.Marshal(expected)
278+
assert.NilError(t, err)
279+
280+
labels := []string{
281+
"label=value",
282+
}
283+
op := withLabels(labels)
284+
285+
config := &mergeBundleConfig{
286+
bundle: &bundle.Bundle{
287+
Parameters: map[string]bundle.Parameter{
288+
internal.ParameterArgs: {},
289+
},
290+
},
291+
params: map[string]string{},
292+
}
293+
err = op(config)
294+
assert.NilError(t, err)
295+
fmt.Println(config.params)
296+
l := config.params[internal.ParameterArgs]
297+
assert.Equal(t, l, string(expectedStr))
298+
}
299+
300+
func TestInvalidLabels(t *testing.T) {
301+
labels := []string{
302+
"com.docker.app.label=value",
303+
}
304+
op := withLabels(labels)
305+
err := op(&mergeBundleConfig{})
306+
assert.ErrorContains(t, err, fmt.Sprintf("labels cannot start with %q", internal.Namespace))
307+
}

internal/commands/run.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type runOptions struct {
2525
kubeNamespace string
2626
stackName string
2727
cnabBundle string
28+
labels []string
2829
}
2930

3031
const longDescription = `Run an App from an App image.`
@@ -59,10 +60,11 @@ func runCmd(dockerCli command.Cli) *cobra.Command {
5960
}
6061
opts.parametersOptions.addFlags(cmd.Flags())
6162
opts.credentialOptions.addFlags(cmd.Flags())
62-
cmd.Flags().StringVar(&opts.orchestrator, "orchestrator", "", "Orchestrator to run on (swarm, kubernetes)")
63-
cmd.Flags().StringVar(&opts.kubeNamespace, "namespace", "default", "Kubernetes namespace in which to run the App")
64-
cmd.Flags().StringVar(&opts.stackName, "name", "", "Name of the running App")
65-
cmd.Flags().StringVar(&opts.cnabBundle, "cnab-bundle-json", "", "Run a CNAB bundle instead of a Docker App image")
63+
cmd.Flags().StringVar(&opts.orchestrator, "orchestrator", "", "Orchestrator to install on (swarm, kubernetes)")
64+
cmd.Flags().StringVar(&opts.kubeNamespace, "namespace", "default", "Kubernetes namespace to install into")
65+
cmd.Flags().StringVar(&opts.stackName, "name", "", "Assign a name to the installation")
66+
cmd.Flags().StringVar(&opts.cnabBundle, "cnab-bundle-json", "", "Run a CNAB bundle instead of a Docker App")
67+
cmd.Flags().StringArrayVar(&opts.labels, "label", nil, "Label to add to services")
6668

6769
return cmd
6870
}
@@ -130,6 +132,7 @@ func runBundle(dockerCli command.Cli, bndl *bundle.Bundle, opts runOptions, ref
130132
if err := mergeBundleParameters(installation,
131133
withFileParameters(opts.parametersFiles),
132134
withCommandLineParameters(opts.overrides),
135+
withLabels(opts.labels),
133136
withOrchestratorParameters(opts.orchestrator, opts.kubeNamespace),
134137
withSendRegistryAuth(opts.sendRegistryAuth),
135138
); err != nil {

internal/names.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ const (
5252
ParameterRenderFormatName = Namespace + "render-format"
5353
// ParameterInspectFormatName is the name of the parameter containing the inspect format
5454
ParameterInspectFormatName = Namespace + "inspect-format"
55+
// ParameterArgs is the name of the parameter containing labels to be applied to service containers
56+
ParameterArgs = Namespace + "args"
5557
// ParameterShareRegistryCredsName is the name of the parameter which indicates if credentials should be shared
5658
ParameterShareRegistryCredsName = Namespace + "share-registry-creds"
5759

@@ -68,6 +70,8 @@ const (
6870
// the inspect output format.
6971
DockerInspectFormatEnvVar = "DOCKER_INSPECT_FORMAT"
7072

73+
DockerArgsPath = "/cnab/app/args.json"
74+
7175
// CustomDockerAppName is the custom variable set by Docker App to
7276
// save custom informations
7377
CustomDockerAppName = "com.docker.app"

internal/packager/cnab.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,24 @@ type DockerAppCustom struct {
2323
Payload json.RawMessage `json:"payload,omitempty"`
2424
}
2525

26+
// DockerAppArgs represent the object passed to the invocation image
27+
// by Docker App.
28+
type DockerAppArgs struct {
29+
// Labels are the labels to add to containers on run
30+
Labels map[string]string `json:"labels,omitempty"`
31+
}
32+
2633
// ToCNAB creates a CNAB bundle from an app package
2734
func ToCNAB(app *types.App, invocationImageName string) (*bundle.Bundle, error) {
2835
mapping := ExtractCNABParameterMapping(app.Parameters())
2936
flatParameters := app.Parameters().Flatten()
3037
definitions := definition.Definitions{
38+
internal.ParameterArgs: {
39+
Type: "string",
40+
Default: "",
41+
Title: "Arguments",
42+
Description: "Arguments that are passed by file to the invocation image",
43+
},
3144
internal.ParameterOrchestratorName: {
3245
Type: "string",
3346
Enum: []interface{}{
@@ -73,6 +86,16 @@ func ToCNAB(app *types.App, invocationImageName string) (*bundle.Bundle, error)
7386
},
7487
}
7588
parameters := map[string]bundle.Parameter{
89+
internal.ParameterArgs: {
90+
Destination: &bundle.Location{
91+
Path: internal.DockerArgsPath,
92+
},
93+
ApplyTo: []string{
94+
"install",
95+
"upgrade",
96+
},
97+
Definition: internal.ParameterArgs,
98+
},
7699
internal.ParameterOrchestratorName: {
77100
Destination: &bundle.Location{
78101
EnvironmentVariable: internal.DockerStackOrchestratorEnvVar,

internal/packager/testdata/bundle-json.golden

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@
5151
"io.cnab.status": {}
5252
},
5353
"parameters": {
54+
"com.docker.app.args": {
55+
"definition": "com.docker.app.args",
56+
"applyTo": [
57+
"install",
58+
"upgrade"
59+
],
60+
"destination": {
61+
"path": "/cnab/app/args.json"
62+
}
63+
},
5464
"com.docker.app.inspect-format": {
5565
"definition": "com.docker.app.inspect-format",
5666
"applyTo": [
@@ -115,6 +125,12 @@
115125
}
116126
},
117127
"definitions": {
128+
"com.docker.app.args": {
129+
"default": "",
130+
"description": "Arguments that are passed by file to the invocation image",
131+
"title": "Arguments",
132+
"type": "string"
133+
},
118134
"com.docker.app.inspect-format": {
119135
"default": "json",
120136
"description": "Output format for the inspect command",

0 commit comments

Comments
 (0)