config_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. package config // import "github.com/docker/docker/integration/config"
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "sort"
  7. "testing"
  8. "time"
  9. "github.com/docker/docker/api/types"
  10. "github.com/docker/docker/api/types/filters"
  11. swarmtypes "github.com/docker/docker/api/types/swarm"
  12. "github.com/docker/docker/client"
  13. "github.com/docker/docker/errdefs"
  14. "github.com/docker/docker/integration/internal/swarm"
  15. "github.com/docker/docker/pkg/stdcopy"
  16. "gotest.tools/v3/assert"
  17. is "gotest.tools/v3/assert/cmp"
  18. "gotest.tools/v3/poll"
  19. "gotest.tools/v3/skip"
  20. )
  21. func TestConfigInspect(t *testing.T) {
  22. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  23. defer setupTest(t)()
  24. d := swarm.NewSwarm(t, testEnv)
  25. defer d.Stop(t)
  26. c := d.NewClientT(t)
  27. defer c.Close()
  28. ctx := context.Background()
  29. testName := t.Name()
  30. configID := createConfig(ctx, t, c, testName, []byte("TESTINGDATA"), nil)
  31. insp, body, err := c.ConfigInspectWithRaw(ctx, configID)
  32. assert.NilError(t, err)
  33. assert.Check(t, is.Equal(insp.Spec.Name, testName))
  34. var config swarmtypes.Config
  35. err = json.Unmarshal(body, &config)
  36. assert.NilError(t, err)
  37. assert.Check(t, is.DeepEqual(config, insp))
  38. }
  39. func TestConfigList(t *testing.T) {
  40. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  41. defer setupTest(t)()
  42. d := swarm.NewSwarm(t, testEnv)
  43. defer d.Stop(t)
  44. c := d.NewClientT(t)
  45. defer c.Close()
  46. ctx := context.Background()
  47. // This test case is ported from the original TestConfigsEmptyList
  48. configs, err := c.ConfigList(ctx, types.ConfigListOptions{})
  49. assert.NilError(t, err)
  50. assert.Check(t, is.Equal(len(configs), 0))
  51. testName0 := "test0-" + t.Name()
  52. testName1 := "test1-" + t.Name()
  53. testNames := []string{testName0, testName1}
  54. sort.Strings(testNames)
  55. // create config test0
  56. createConfig(ctx, t, c, testName0, []byte("TESTINGDATA0"), map[string]string{"type": "test"})
  57. config1ID := createConfig(ctx, t, c, testName1, []byte("TESTINGDATA1"), map[string]string{"type": "production"})
  58. // test by `config ls`
  59. entries, err := c.ConfigList(ctx, types.ConfigListOptions{})
  60. assert.NilError(t, err)
  61. assert.Check(t, is.DeepEqual(configNamesFromList(entries), testNames))
  62. testCases := []struct {
  63. filters filters.Args
  64. expected []string
  65. }{
  66. // test filter by name `config ls --filter name=xxx`
  67. {
  68. filters: filters.NewArgs(filters.Arg("name", testName0)),
  69. expected: []string{testName0},
  70. },
  71. // test filter by id `config ls --filter id=xxx`
  72. {
  73. filters: filters.NewArgs(filters.Arg("id", config1ID)),
  74. expected: []string{testName1},
  75. },
  76. // test filter by label `config ls --filter label=xxx`
  77. {
  78. filters: filters.NewArgs(filters.Arg("label", "type")),
  79. expected: testNames,
  80. },
  81. {
  82. filters: filters.NewArgs(filters.Arg("label", "type=test")),
  83. expected: []string{testName0},
  84. },
  85. {
  86. filters: filters.NewArgs(filters.Arg("label", "type=production")),
  87. expected: []string{testName1},
  88. },
  89. }
  90. for _, tc := range testCases {
  91. entries, err = c.ConfigList(ctx, types.ConfigListOptions{
  92. Filters: tc.filters,
  93. })
  94. assert.NilError(t, err)
  95. assert.Check(t, is.DeepEqual(configNamesFromList(entries), tc.expected))
  96. }
  97. }
  98. func createConfig(ctx context.Context, t *testing.T, client client.APIClient, name string, data []byte, labels map[string]string) string {
  99. config, err := client.ConfigCreate(ctx, swarmtypes.ConfigSpec{
  100. Annotations: swarmtypes.Annotations{
  101. Name: name,
  102. Labels: labels,
  103. },
  104. Data: data,
  105. })
  106. assert.NilError(t, err)
  107. assert.Check(t, config.ID != "")
  108. return config.ID
  109. }
  110. func TestConfigsCreateAndDelete(t *testing.T) {
  111. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  112. defer setupTest(t)()
  113. d := swarm.NewSwarm(t, testEnv)
  114. defer d.Stop(t)
  115. c := d.NewClientT(t)
  116. defer c.Close()
  117. ctx := context.Background()
  118. testName := "test_config-" + t.Name()
  119. configID := createConfig(ctx, t, c, testName, []byte("TESTINGDATA"), nil)
  120. err := c.ConfigRemove(ctx, configID)
  121. assert.NilError(t, err)
  122. _, _, err = c.ConfigInspectWithRaw(ctx, configID)
  123. assert.Check(t, errdefs.IsNotFound(err))
  124. assert.Check(t, is.ErrorContains(err, configID))
  125. err = c.ConfigRemove(ctx, "non-existing")
  126. assert.Check(t, errdefs.IsNotFound(err))
  127. assert.Check(t, is.ErrorContains(err, "non-existing"))
  128. testName = "test_secret_with_labels_" + t.Name()
  129. configID = createConfig(ctx, t, c, testName, []byte("TESTINGDATA"), map[string]string{
  130. "key1": "value1",
  131. "key2": "value2",
  132. })
  133. insp, _, err := c.ConfigInspectWithRaw(ctx, configID)
  134. assert.NilError(t, err)
  135. assert.Check(t, is.Equal(insp.Spec.Name, testName))
  136. assert.Check(t, is.Equal(len(insp.Spec.Labels), 2))
  137. assert.Check(t, is.Equal(insp.Spec.Labels["key1"], "value1"))
  138. assert.Check(t, is.Equal(insp.Spec.Labels["key2"], "value2"))
  139. }
  140. func TestConfigsUpdate(t *testing.T) {
  141. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  142. defer setupTest(t)()
  143. d := swarm.NewSwarm(t, testEnv)
  144. defer d.Stop(t)
  145. c := d.NewClientT(t)
  146. defer c.Close()
  147. ctx := context.Background()
  148. testName := "test_config-" + t.Name()
  149. configID := createConfig(ctx, t, c, testName, []byte("TESTINGDATA"), nil)
  150. insp, _, err := c.ConfigInspectWithRaw(ctx, configID)
  151. assert.NilError(t, err)
  152. assert.Check(t, is.Equal(insp.ID, configID))
  153. // test UpdateConfig with full ID
  154. insp.Spec.Labels = map[string]string{"test": "test1"}
  155. err = c.ConfigUpdate(ctx, configID, insp.Version, insp.Spec)
  156. assert.NilError(t, err)
  157. insp, _, err = c.ConfigInspectWithRaw(ctx, configID)
  158. assert.NilError(t, err)
  159. assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test1"))
  160. // test UpdateConfig with full name
  161. insp.Spec.Labels = map[string]string{"test": "test2"}
  162. err = c.ConfigUpdate(ctx, testName, insp.Version, insp.Spec)
  163. assert.NilError(t, err)
  164. insp, _, err = c.ConfigInspectWithRaw(ctx, configID)
  165. assert.NilError(t, err)
  166. assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test2"))
  167. // test UpdateConfig with prefix ID
  168. insp.Spec.Labels = map[string]string{"test": "test3"}
  169. err = c.ConfigUpdate(ctx, configID[:1], insp.Version, insp.Spec)
  170. assert.NilError(t, err)
  171. insp, _, err = c.ConfigInspectWithRaw(ctx, configID)
  172. assert.NilError(t, err)
  173. assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test3"))
  174. // test UpdateConfig in updating Data which is not supported in daemon
  175. // this test will produce an error in func UpdateConfig
  176. insp.Spec.Data = []byte("TESTINGDATA2")
  177. err = c.ConfigUpdate(ctx, configID, insp.Version, insp.Spec)
  178. assert.Check(t, errdefs.IsInvalidParameter(err))
  179. assert.Check(t, is.ErrorContains(err, "only updates to Labels are allowed"))
  180. }
  181. func TestTemplatedConfig(t *testing.T) {
  182. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  183. d := swarm.NewSwarm(t, testEnv)
  184. defer d.Stop(t)
  185. c := d.NewClientT(t)
  186. defer c.Close()
  187. ctx := context.Background()
  188. referencedSecretName := "referencedsecret-" + t.Name()
  189. referencedSecretSpec := swarmtypes.SecretSpec{
  190. Annotations: swarmtypes.Annotations{
  191. Name: referencedSecretName,
  192. },
  193. Data: []byte("this is a secret"),
  194. }
  195. referencedSecret, err := c.SecretCreate(ctx, referencedSecretSpec)
  196. assert.Check(t, err)
  197. referencedConfigName := "referencedconfig-" + t.Name()
  198. referencedConfigSpec := swarmtypes.ConfigSpec{
  199. Annotations: swarmtypes.Annotations{
  200. Name: referencedConfigName,
  201. },
  202. Data: []byte("this is a config"),
  203. }
  204. referencedConfig, err := c.ConfigCreate(ctx, referencedConfigSpec)
  205. assert.Check(t, err)
  206. templatedConfigName := "templated_config-" + t.Name()
  207. configSpec := swarmtypes.ConfigSpec{
  208. Annotations: swarmtypes.Annotations{
  209. Name: templatedConfigName,
  210. },
  211. Templating: &swarmtypes.Driver{
  212. Name: "golang",
  213. },
  214. Data: []byte("SERVICE_NAME={{.Service.Name}}\n" +
  215. "{{secret \"referencedsecrettarget\"}}\n" +
  216. "{{config \"referencedconfigtarget\"}}\n"),
  217. }
  218. templatedConfig, err := c.ConfigCreate(ctx, configSpec)
  219. assert.Check(t, err)
  220. serviceName := "svc_" + t.Name()
  221. serviceID := swarm.CreateService(t, d,
  222. swarm.ServiceWithConfig(
  223. &swarmtypes.ConfigReference{
  224. File: &swarmtypes.ConfigReferenceFileTarget{
  225. Name: "templated_config",
  226. UID: "0",
  227. GID: "0",
  228. Mode: 0600,
  229. },
  230. ConfigID: templatedConfig.ID,
  231. ConfigName: templatedConfigName,
  232. },
  233. ),
  234. swarm.ServiceWithConfig(
  235. &swarmtypes.ConfigReference{
  236. File: &swarmtypes.ConfigReferenceFileTarget{
  237. Name: "referencedconfigtarget",
  238. UID: "0",
  239. GID: "0",
  240. Mode: 0600,
  241. },
  242. ConfigID: referencedConfig.ID,
  243. ConfigName: referencedConfigName,
  244. },
  245. ),
  246. swarm.ServiceWithSecret(
  247. &swarmtypes.SecretReference{
  248. File: &swarmtypes.SecretReferenceFileTarget{
  249. Name: "referencedsecrettarget",
  250. UID: "0",
  251. GID: "0",
  252. Mode: 0600,
  253. },
  254. SecretID: referencedSecret.ID,
  255. SecretName: referencedSecretName,
  256. },
  257. ),
  258. swarm.ServiceWithName(serviceName),
  259. )
  260. poll.WaitOn(t, swarm.RunningTasksCount(c, serviceID, 1), swarm.ServicePoll, poll.WithTimeout(1*time.Minute))
  261. tasks := swarm.GetRunningTasks(t, c, serviceID)
  262. assert.Assert(t, len(tasks) > 0, "no running tasks found for service %s", serviceID)
  263. attach := swarm.ExecTask(t, d, tasks[0], types.ExecConfig{
  264. Cmd: []string{"/bin/cat", "/templated_config"},
  265. AttachStdout: true,
  266. AttachStderr: true,
  267. })
  268. expect := "SERVICE_NAME=" + serviceName + "\n" +
  269. "this is a secret\n" +
  270. "this is a config\n"
  271. assertAttachedStream(t, attach, expect)
  272. attach = swarm.ExecTask(t, d, tasks[0], types.ExecConfig{
  273. Cmd: []string{"mount"},
  274. AttachStdout: true,
  275. AttachStderr: true,
  276. })
  277. assertAttachedStream(t, attach, "tmpfs on /templated_config type tmpfs")
  278. }
  279. // Test case for 28884
  280. func TestConfigCreateResolve(t *testing.T) {
  281. skip.If(t, testEnv.DaemonInfo.OSType != "linux")
  282. defer setupTest(t)()
  283. d := swarm.NewSwarm(t, testEnv)
  284. defer d.Stop(t)
  285. c := d.NewClientT(t)
  286. defer c.Close()
  287. ctx := context.Background()
  288. configName := "test_config_" + t.Name()
  289. configID := createConfig(ctx, t, c, configName, []byte("foo"), nil)
  290. fakeName := configID
  291. fakeID := createConfig(ctx, t, c, fakeName, []byte("fake foo"), nil)
  292. entries, err := c.ConfigList(ctx, types.ConfigListOptions{})
  293. assert.NilError(t, err)
  294. assert.Assert(t, is.Contains(configNamesFromList(entries), configName))
  295. assert.Assert(t, is.Contains(configNamesFromList(entries), fakeName))
  296. err = c.ConfigRemove(ctx, configID)
  297. assert.NilError(t, err)
  298. // Fake one will remain
  299. entries, err = c.ConfigList(ctx, types.ConfigListOptions{})
  300. assert.NilError(t, err)
  301. assert.Assert(t, is.DeepEqual(configNamesFromList(entries), []string{fakeName}))
  302. // Remove based on name prefix of the fake one
  303. // (which is the same as the ID of foo one) should not work
  304. // as search is only done based on:
  305. // - Full ID
  306. // - Full Name
  307. // - Partial ID (prefix)
  308. err = c.ConfigRemove(ctx, configID[:5])
  309. assert.Assert(t, nil != err)
  310. entries, err = c.ConfigList(ctx, types.ConfigListOptions{})
  311. assert.NilError(t, err)
  312. assert.Assert(t, is.DeepEqual(configNamesFromList(entries), []string{fakeName}))
  313. // Remove based on ID prefix of the fake one should succeed
  314. err = c.ConfigRemove(ctx, fakeID[:5])
  315. assert.NilError(t, err)
  316. entries, err = c.ConfigList(ctx, types.ConfigListOptions{})
  317. assert.NilError(t, err)
  318. assert.Assert(t, is.Equal(0, len(entries)))
  319. }
  320. func assertAttachedStream(t *testing.T, attach types.HijackedResponse, expect string) {
  321. buf := bytes.NewBuffer(nil)
  322. _, err := stdcopy.StdCopy(buf, buf, attach.Reader)
  323. assert.NilError(t, err)
  324. assert.Check(t, is.Contains(buf.String(), expect))
  325. }
  326. func configNamesFromList(entries []swarmtypes.Config) []string {
  327. var values []string
  328. for _, entry := range entries {
  329. values = append(values, entry.Spec.Name)
  330. }
  331. sort.Strings(values)
  332. return values
  333. }