| package config |
| |
| import ( |
| "bytes" |
| "context" |
| "encoding/json" |
| "sort" |
| "testing" |
| "time" |
| |
| cerrdefs "github.com/containerd/errdefs" |
| "github.com/moby/moby/api/pkg/stdcopy" |
| swarmtypes "github.com/moby/moby/api/types/swarm" |
| "github.com/moby/moby/client" |
| "github.com/moby/moby/v2/integration/internal/swarm" |
| "github.com/moby/moby/v2/internal/testutil" |
| "gotest.tools/v3/assert" |
| is "gotest.tools/v3/assert/cmp" |
| "gotest.tools/v3/poll" |
| "gotest.tools/v3/skip" |
| ) |
| |
| func TestConfigInspect(t *testing.T) { |
| skip.If(t, testEnv.DaemonInfo.OSType == "windows") |
| |
| ctx := setupTest(t) |
| |
| d := swarm.NewSwarm(ctx, t, testEnv) |
| defer d.Stop(t) |
| c := d.NewClientT(t) |
| defer c.Close() |
| |
| testName := t.Name() |
| configID := createConfig(ctx, t, c, testName, []byte("TESTINGDATA"), nil) |
| |
| result, err := c.ConfigInspect(ctx, configID, client.ConfigInspectOptions{}) |
| assert.NilError(t, err) |
| assert.Check(t, is.Equal(result.Config.Spec.Name, testName)) |
| |
| var config swarmtypes.Config |
| err = json.Unmarshal(result.Raw, &config) |
| assert.NilError(t, err) |
| assert.Check(t, is.DeepEqual(config, result.Config)) |
| } |
| |
| func TestConfigList(t *testing.T) { |
| skip.If(t, testEnv.DaemonInfo.OSType == "windows") |
| |
| ctx := setupTest(t) |
| |
| d := swarm.NewSwarm(ctx, t, testEnv) |
| defer d.Stop(t) |
| c := d.NewClientT(t) |
| defer c.Close() |
| |
| // This test case is ported from the original TestConfigsEmptyList |
| result, err := c.ConfigList(ctx, client.ConfigListOptions{}) |
| assert.NilError(t, err) |
| assert.Check(t, is.Equal(len(result.Items), 0)) |
| |
| testName0 := "test0-" + t.Name() |
| testName1 := "test1-" + t.Name() |
| testNames := []string{testName0, testName1} |
| sort.Strings(testNames) |
| |
| // create config test0 |
| createConfig(ctx, t, c, testName0, []byte("TESTINGDATA0"), map[string]string{"type": "test"}) |
| |
| config1ID := createConfig(ctx, t, c, testName1, []byte("TESTINGDATA1"), map[string]string{"type": "production"}) |
| |
| // test by `config ls` |
| res, err := c.ConfigList(ctx, client.ConfigListOptions{}) |
| assert.NilError(t, err) |
| assert.Check(t, is.DeepEqual(configNamesFromList(res.Items), testNames)) |
| |
| testCases := []struct { |
| desc string |
| filters client.Filters |
| expected []string |
| }{ |
| { |
| desc: "test filter by name", |
| filters: make(client.Filters).Add("name", testName0), |
| expected: []string{testName0}, |
| }, |
| { |
| desc: "test filter by id", |
| filters: make(client.Filters).Add("id", config1ID), |
| expected: []string{testName1}, |
| }, |
| { |
| desc: "test filter by label key only", |
| filters: make(client.Filters).Add("label", "type"), |
| expected: testNames, |
| }, |
| { |
| desc: "test filter by label key=value " + testName0, |
| filters: make(client.Filters).Add("label", "type=test"), |
| expected: []string{testName0}, |
| }, |
| { |
| desc: "test filter by label key=value " + testName1, |
| filters: make(client.Filters).Add("label", "type=production"), |
| expected: []string{testName1}, |
| }, |
| } |
| for _, tc := range testCases { |
| t.Run(tc.desc, func(t *testing.T) { |
| ctx := testutil.StartSpan(ctx, t) |
| res, err = c.ConfigList(ctx, client.ConfigListOptions{ |
| Filters: tc.filters, |
| }) |
| assert.NilError(t, err) |
| assert.Check(t, is.DeepEqual(configNamesFromList(res.Items), tc.expected)) |
| }) |
| } |
| } |
| |
| func createConfig(ctx context.Context, t *testing.T, apiClient client.APIClient, name string, data []byte, labels map[string]string) string { |
| result, err := apiClient.ConfigCreate(ctx, client.ConfigCreateOptions{ |
| Spec: swarmtypes.ConfigSpec{ |
| Annotations: swarmtypes.Annotations{ |
| Name: name, |
| Labels: labels, |
| }, |
| Data: data, |
| }, |
| }) |
| assert.NilError(t, err) |
| assert.Check(t, result.ID != "") |
| return result.ID |
| } |
| |
| func TestConfigsCreateAndDelete(t *testing.T) { |
| skip.If(t, testEnv.DaemonInfo.OSType == "windows") |
| |
| ctx := setupTest(t) |
| d := swarm.NewSwarm(ctx, t, testEnv) |
| defer d.Stop(t) |
| c := d.NewClientT(t) |
| defer c.Close() |
| |
| testName := "test_config-" + t.Name() |
| configID := createConfig(ctx, t, c, testName, []byte("TESTINGDATA"), nil) |
| |
| _, err := c.ConfigRemove(ctx, configID, client.ConfigRemoveOptions{}) |
| assert.NilError(t, err) |
| |
| _, err = c.ConfigInspect(ctx, configID, client.ConfigInspectOptions{}) |
| assert.Check(t, cerrdefs.IsNotFound(err)) |
| assert.Check(t, is.ErrorContains(err, configID)) |
| |
| _, err = c.ConfigRemove(ctx, "non-existing", client.ConfigRemoveOptions{}) |
| assert.Check(t, cerrdefs.IsNotFound(err)) |
| assert.Check(t, is.ErrorContains(err, "non-existing")) |
| |
| testName = "test_secret_with_labels_" + t.Name() |
| configID = createConfig(ctx, t, c, testName, []byte("TESTINGDATA"), map[string]string{ |
| "key1": "value1", |
| "key2": "value2", |
| }) |
| |
| result, err := c.ConfigInspect(ctx, configID, client.ConfigInspectOptions{}) |
| assert.NilError(t, err) |
| assert.Check(t, is.Equal(result.Config.Spec.Name, testName)) |
| assert.Check(t, is.Equal(len(result.Config.Spec.Labels), 2)) |
| assert.Check(t, is.Equal(result.Config.Spec.Labels["key1"], "value1")) |
| assert.Check(t, is.Equal(result.Config.Spec.Labels["key2"], "value2")) |
| } |
| |
| func TestConfigsUpdate(t *testing.T) { |
| skip.If(t, testEnv.DaemonInfo.OSType == "windows") |
| |
| ctx := setupTest(t) |
| |
| d := swarm.NewSwarm(ctx, t, testEnv) |
| defer d.Stop(t) |
| c := d.NewClientT(t) |
| defer c.Close() |
| |
| testName := "test_config-" + t.Name() |
| configID := createConfig(ctx, t, c, testName, []byte("TESTINGDATA"), nil) |
| |
| insp, err := c.ConfigInspect(ctx, configID, client.ConfigInspectOptions{}) |
| assert.NilError(t, err) |
| assert.Check(t, is.Equal(insp.Config.ID, configID)) |
| |
| // test UpdateConfig with full ID |
| insp.Config.Spec.Labels = map[string]string{"test": "test1"} |
| _, err = c.ConfigUpdate(ctx, configID, client.ConfigUpdateOptions{Version: insp.Config.Version, Spec: insp.Config.Spec}) |
| assert.NilError(t, err) |
| |
| insp, err = c.ConfigInspect(ctx, configID, client.ConfigInspectOptions{}) |
| assert.NilError(t, err) |
| assert.Check(t, is.Equal(insp.Config.Spec.Labels["test"], "test1")) |
| |
| // test UpdateConfig with full name |
| insp.Config.Spec.Labels = map[string]string{"test": "test2"} |
| _, err = c.ConfigUpdate(ctx, testName, client.ConfigUpdateOptions{Version: insp.Config.Version, Spec: insp.Config.Spec}) |
| assert.NilError(t, err) |
| |
| insp, err = c.ConfigInspect(ctx, configID, client.ConfigInspectOptions{}) |
| assert.NilError(t, err) |
| assert.Check(t, is.Equal(insp.Config.Spec.Labels["test"], "test2")) |
| |
| // test UpdateConfig with prefix ID |
| insp.Config.Spec.Labels = map[string]string{"test": "test3"} |
| _, err = c.ConfigUpdate(ctx, configID[:1], client.ConfigUpdateOptions{Version: insp.Config.Version, Spec: insp.Config.Spec}) |
| assert.NilError(t, err) |
| |
| insp, err = c.ConfigInspect(ctx, configID, client.ConfigInspectOptions{}) |
| assert.NilError(t, err) |
| assert.Check(t, is.Equal(insp.Config.Spec.Labels["test"], "test3")) |
| |
| // test UpdateConfig in updating Data which is not supported in daemon |
| // this test will produce an error in func UpdateConfig |
| insp.Config.Spec.Data = []byte("TESTINGDATA2") |
| _, err = c.ConfigUpdate(ctx, configID, client.ConfigUpdateOptions{Version: insp.Config.Version, Spec: insp.Config.Spec}) |
| assert.Check(t, cerrdefs.IsInvalidArgument(err)) |
| assert.Check(t, is.ErrorContains(err, "only updates to Labels are allowed")) |
| } |
| |
| func TestTemplatedConfig(t *testing.T) { |
| skip.If(t, testEnv.DaemonInfo.OSType == "windows") |
| ctx := testutil.StartSpan(baseContext, t) |
| |
| d := swarm.NewSwarm(ctx, t, testEnv) |
| defer d.Stop(t) |
| c := d.NewClientT(t) |
| defer c.Close() |
| |
| referencedSecretName := "referencedsecret-" + t.Name() |
| referencedSecretSpec := swarmtypes.SecretSpec{ |
| Annotations: swarmtypes.Annotations{ |
| Name: referencedSecretName, |
| }, |
| Data: []byte("this is a secret"), |
| } |
| referencedSecret, err := c.SecretCreate(ctx, client.SecretCreateOptions{ |
| Spec: referencedSecretSpec, |
| }) |
| assert.Check(t, err) |
| |
| referencedConfigName := "referencedconfig-" + t.Name() |
| referencedConfigSpec := swarmtypes.ConfigSpec{ |
| Annotations: swarmtypes.Annotations{ |
| Name: referencedConfigName, |
| }, |
| Data: []byte("this is a config"), |
| } |
| referencedConfigResult, err := c.ConfigCreate(ctx, client.ConfigCreateOptions{ |
| Spec: referencedConfigSpec, |
| }) |
| assert.Check(t, err) |
| |
| templatedConfigName := "templated_config-" + t.Name() |
| configSpec := swarmtypes.ConfigSpec{ |
| Annotations: swarmtypes.Annotations{ |
| Name: templatedConfigName, |
| }, |
| Templating: &swarmtypes.Driver{ |
| Name: "golang", |
| }, |
| Data: []byte(`SERVICE_NAME={{.Service.Name}} |
| {{secret "referencedsecrettarget"}} |
| {{config "referencedconfigtarget"}} |
| `), |
| } |
| |
| templatedConfigResult, err := c.ConfigCreate(ctx, client.ConfigCreateOptions{ |
| Spec: configSpec, |
| }) |
| assert.Check(t, err) |
| |
| const serviceName = "svc_templated_config" |
| serviceID := swarm.CreateService(ctx, t, d, |
| swarm.ServiceWithConfig( |
| &swarmtypes.ConfigReference{ |
| File: &swarmtypes.ConfigReferenceFileTarget{ |
| Name: "templated_config", |
| UID: "0", |
| GID: "0", |
| Mode: 0o600, |
| }, |
| ConfigID: templatedConfigResult.ID, |
| ConfigName: templatedConfigName, |
| }, |
| ), |
| swarm.ServiceWithConfig( |
| &swarmtypes.ConfigReference{ |
| File: &swarmtypes.ConfigReferenceFileTarget{ |
| Name: "referencedconfigtarget", |
| UID: "0", |
| GID: "0", |
| Mode: 0o600, |
| }, |
| ConfigID: referencedConfigResult.ID, |
| ConfigName: referencedConfigName, |
| }, |
| ), |
| swarm.ServiceWithSecret( |
| &swarmtypes.SecretReference{ |
| File: &swarmtypes.SecretReferenceFileTarget{ |
| Name: "referencedsecrettarget", |
| UID: "0", |
| GID: "0", |
| Mode: 0o600, |
| }, |
| SecretID: referencedSecret.ID, |
| SecretName: referencedSecretName, |
| }, |
| ), |
| swarm.ServiceWithName(serviceName), |
| ) |
| |
| poll.WaitOn(t, swarm.RunningTasksCount(ctx, c, serviceID, 1), swarm.ServicePoll, poll.WithTimeout(1*time.Minute)) |
| |
| tasks := swarm.GetRunningTasks(ctx, t, c, serviceID) |
| assert.Assert(t, len(tasks) > 0, "no running tasks found for service %s", serviceID) |
| |
| resp := swarm.ExecTask(ctx, t, d, tasks[0], client.ExecCreateOptions{ |
| Cmd: []string{"/bin/cat", "/templated_config"}, |
| AttachStdout: true, |
| AttachStderr: true, |
| }) |
| |
| const expect = "SERVICE_NAME=" + serviceName + "\nthis is a secret\nthis is a config\n" |
| var outBuf, errBuf bytes.Buffer |
| _, err = stdcopy.StdCopy(&outBuf, &errBuf, resp.Reader) |
| assert.NilError(t, err) |
| assert.Check(t, is.Equal(outBuf.String(), expect)) |
| assert.Check(t, is.Equal(errBuf.String(), "")) |
| |
| outBuf.Reset() |
| errBuf.Reset() |
| resp = swarm.ExecTask(ctx, t, d, tasks[0], client.ExecCreateOptions{ |
| Cmd: []string{"mount"}, |
| AttachStdout: true, |
| AttachStderr: true, |
| }) |
| |
| _, err = stdcopy.StdCopy(&outBuf, &errBuf, resp.Reader) |
| assert.NilError(t, err) |
| assert.Check(t, is.Contains(outBuf.String(), "tmpfs on /templated_config type tmpfs"), "expected to be mounted as tmpfs") |
| assert.Check(t, is.Equal(errBuf.String(), "")) |
| } |
| |
| // Test case for 28884 |
| func TestConfigCreateResolve(t *testing.T) { |
| skip.If(t, testEnv.DaemonInfo.OSType != "linux") |
| |
| ctx := setupTest(t) |
| |
| d := swarm.NewSwarm(ctx, t, testEnv) |
| defer d.Stop(t) |
| c := d.NewClientT(t) |
| defer c.Close() |
| |
| configName := "test_config_" + t.Name() |
| configID := createConfig(ctx, t, c, configName, []byte("foo"), nil) |
| |
| fakeName := configID |
| fakeID := createConfig(ctx, t, c, fakeName, []byte("fake foo"), nil) |
| |
| res, err := c.ConfigList(ctx, client.ConfigListOptions{}) |
| assert.NilError(t, err) |
| assert.Assert(t, is.Contains(configNamesFromList(res.Items), configName)) |
| assert.Assert(t, is.Contains(configNamesFromList(res.Items), fakeName)) |
| |
| _, err = c.ConfigRemove(ctx, configID, client.ConfigRemoveOptions{}) |
| assert.NilError(t, err) |
| |
| // Fake one will remain |
| res, err = c.ConfigList(ctx, client.ConfigListOptions{}) |
| assert.NilError(t, err) |
| assert.Assert(t, is.DeepEqual(configNamesFromList(res.Items), []string{fakeName})) |
| |
| // Remove based on name prefix of the fake one |
| // (which is the same as the ID of foo one) should not work |
| // as search is only done based on: |
| // - Full ID |
| // - Full Name |
| // - Partial ID (prefix) |
| _, err = c.ConfigRemove(ctx, configID[:5], client.ConfigRemoveOptions{}) |
| assert.Assert(t, err != nil) |
| res, err = c.ConfigList(ctx, client.ConfigListOptions{}) |
| assert.NilError(t, err) |
| assert.Assert(t, is.DeepEqual(configNamesFromList(res.Items), []string{fakeName})) |
| |
| // Remove based on ID prefix of the fake one should succeed |
| _, err = c.ConfigRemove(ctx, fakeID[:5], client.ConfigRemoveOptions{}) |
| assert.NilError(t, err) |
| res, err = c.ConfigList(ctx, client.ConfigListOptions{}) |
| assert.NilError(t, err) |
| assert.Assert(t, is.Equal(0, len(res.Items))) |
| } |
| |
| func configNamesFromList(entries []swarmtypes.Config) []string { |
| var values []string |
| for _, entry := range entries { |
| values = append(values, entry.Spec.Name) |
| } |
| sort.Strings(values) |
| return values |
| } |