ImageBuilderTests.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. using System.Runtime.CompilerServices;
  4. using System.Text.Json.Nodes;
  5. using Microsoft.NET.TestFramework;
  6. using Xunit;
  7. using Xunit.Abstractions;
  8. namespace Microsoft.NET.Build.Containers.UnitTests;
  9. public class ImageBuilderTests
  10. {
  11. private readonly TestLoggerFactory _loggerFactory;
  12. private static readonly string StaticKnownDigestValue = "sha256:338c0b702da88157ba4bb706678e43346ece2e4397b888d59fb2d9f6113c8070";
  13. public ImageBuilderTests(ITestOutputHelper output)
  14. {
  15. _loggerFactory = new TestLoggerFactory(output);
  16. }
  17. [Fact]
  18. public void CanAddLabelsToImage()
  19. {
  20. string simpleImageConfig =
  21. """
  22. {
  23. "architecture": "amd64",
  24. "config": {
  25. "Hostname": "",
  26. "Domainname": "",
  27. "User": "",
  28. "AttachStdin": false,
  29. "AttachStdout": false,
  30. "AttachStderr": false,
  31. "Tty": false,
  32. "OpenStdin": false,
  33. "StdinOnce": false,
  34. "Env": [
  35. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  36. "ASPNETCORE_URLS=http://+:80",
  37. "DOTNET_RUNNING_IN_CONTAINER=true",
  38. "DOTNET_VERSION=7.0.2",
  39. "ASPNET_VERSION=7.0.2"
  40. ],
  41. "Cmd": ["bash"],
  42. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  43. "Volumes": null,
  44. "WorkingDir": "",
  45. "Entrypoint": null,
  46. "OnBuild": null,
  47. "Labels": null
  48. },
  49. "created": "2023-02-04T08:14:52.000901321Z",
  50. "os": "linux",
  51. "rootfs": {
  52. "type": "layers",
  53. "diff_ids": [
  54. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  55. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  56. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  57. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  58. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  59. ]
  60. }
  61. }
  62. """;
  63. JsonNode? node = JsonNode.Parse(simpleImageConfig);
  64. Assert.NotNull(node);
  65. ImageConfig baseConfig = new(node);
  66. baseConfig.AddLabel("testLabel1", "v1");
  67. baseConfig.AddLabel("testLabel2", "v2");
  68. string readyImage = baseConfig.BuildConfig();
  69. JsonNode? result = JsonNode.Parse(readyImage);
  70. var resultLabels = result?["config"]?["Labels"] as JsonObject;
  71. Assert.NotNull(resultLabels);
  72. Assert.Equal(2, resultLabels.Count);
  73. Assert.Equal("v1", resultLabels["testLabel1"]?.ToString());
  74. Assert.Equal("v2", resultLabels["testLabel2"]?.ToString());
  75. }
  76. [Fact]
  77. public void CanPreserveExistingLabels()
  78. {
  79. string simpleImageConfig =
  80. """
  81. {
  82. "architecture": "amd64",
  83. "config": {
  84. "Hostname": "",
  85. "Domainname": "",
  86. "User": "",
  87. "AttachStdin": false,
  88. "AttachStdout": false,
  89. "AttachStderr": false,
  90. "Tty": false,
  91. "OpenStdin": false,
  92. "StdinOnce": false,
  93. "Env": [
  94. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  95. "ASPNETCORE_URLS=http://+:80",
  96. "DOTNET_RUNNING_IN_CONTAINER=true",
  97. "DOTNET_VERSION=7.0.2",
  98. "ASPNET_VERSION=7.0.2"
  99. ],
  100. "Cmd": ["bash"],
  101. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  102. "Volumes": null,
  103. "WorkingDir": "",
  104. "Entrypoint": null,
  105. "OnBuild": null,
  106. "Labels":
  107. {
  108. "existing" : "e1",
  109. "existing2" : "e2"
  110. }
  111. },
  112. "created": "2023-02-04T08:14:52.000901321Z",
  113. "os": "linux",
  114. "rootfs": {
  115. "type": "layers",
  116. "diff_ids": [
  117. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  118. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  119. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  120. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  121. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  122. ]
  123. }
  124. }
  125. """;
  126. JsonNode? node = JsonNode.Parse(simpleImageConfig);
  127. Assert.NotNull(node);
  128. ImageConfig baseConfig = new(node);
  129. baseConfig.AddLabel("testLabel1", "v1");
  130. baseConfig.AddLabel("existing2", "v2");
  131. string readyImage = baseConfig.BuildConfig();
  132. JsonNode? result = JsonNode.Parse(readyImage);
  133. var resultLabels = result?["config"]?["Labels"] as JsonObject;
  134. Assert.NotNull(resultLabels);
  135. Assert.Equal(3, resultLabels.Count);
  136. Assert.Equal("v1", resultLabels["testLabel1"]?.ToString());
  137. Assert.Equal("v2", resultLabels["existing2"]?.ToString());
  138. Assert.Equal("e1", resultLabels["existing"]?.ToString());
  139. }
  140. [Fact]
  141. public void CanAddPortsToImage()
  142. {
  143. string simpleImageConfig =
  144. """
  145. {
  146. "architecture": "amd64",
  147. "config": {
  148. "Hostname": "",
  149. "Domainname": "",
  150. "User": "",
  151. "AttachStdin": false,
  152. "AttachStdout": false,
  153. "AttachStderr": false,
  154. "Tty": false,
  155. "OpenStdin": false,
  156. "StdinOnce": false,
  157. "Env": [
  158. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  159. "ASPNETCORE_URLS=http://+:80",
  160. "DOTNET_RUNNING_IN_CONTAINER=true",
  161. "DOTNET_VERSION=7.0.2",
  162. "ASPNET_VERSION=7.0.2"
  163. ],
  164. "Cmd": ["bash"],
  165. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  166. "Volumes": null,
  167. "WorkingDir": "",
  168. "Entrypoint": null,
  169. "OnBuild": null,
  170. "Labels": null
  171. },
  172. "created": "2023-02-04T08:14:52.000901321Z",
  173. "os": "linux",
  174. "rootfs": {
  175. "type": "layers",
  176. "diff_ids": [
  177. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  178. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  179. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  180. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  181. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  182. ]
  183. }
  184. }
  185. """;
  186. JsonNode? node = JsonNode.Parse(simpleImageConfig);
  187. Assert.NotNull(node);
  188. ImageConfig baseConfig = new(node);
  189. baseConfig.ExposePort(6000, PortType.tcp);
  190. baseConfig.ExposePort(6010, PortType.udp);
  191. string readyImage = baseConfig.BuildConfig();
  192. JsonNode? result = JsonNode.Parse(readyImage);
  193. var resultPorts = result?["config"]?["ExposedPorts"] as JsonObject;
  194. Assert.NotNull(resultPorts);
  195. Assert.Equal(2, resultPorts.Count);
  196. Assert.NotNull(resultPorts["6000/tcp"] as JsonObject);
  197. Assert.NotNull(resultPorts["6010/udp"] as JsonObject);
  198. }
  199. [Fact]
  200. public void CanPreserveExistingPorts()
  201. {
  202. string simpleImageConfig =
  203. """
  204. {
  205. "architecture": "amd64",
  206. "config": {
  207. "Hostname": "",
  208. "Domainname": "",
  209. "User": "",
  210. "AttachStdin": false,
  211. "AttachStdout": false,
  212. "AttachStderr": false,
  213. "Tty": false,
  214. "OpenStdin": false,
  215. "StdinOnce": false,
  216. "Env": [
  217. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  218. "ASPNETCORE_URLS=http://+:80",
  219. "DOTNET_RUNNING_IN_CONTAINER=true",
  220. "DOTNET_VERSION=7.0.2",
  221. "ASPNET_VERSION=7.0.2"
  222. ],
  223. "Cmd": ["bash"],
  224. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  225. "Volumes": null,
  226. "WorkingDir": "",
  227. "Entrypoint": null,
  228. "OnBuild": null,
  229. "Labels": null,
  230. "ExposedPorts":
  231. {
  232. "6100/tcp": {},
  233. "6200": {}
  234. }
  235. },
  236. "created": "2023-02-04T08:14:52.000901321Z",
  237. "os": "linux",
  238. "rootfs": {
  239. "type": "layers",
  240. "diff_ids": [
  241. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  242. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  243. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  244. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  245. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  246. ]
  247. }
  248. }
  249. """;
  250. JsonNode? node = JsonNode.Parse(simpleImageConfig);
  251. Assert.NotNull(node);
  252. ImageConfig baseConfig = new(node);
  253. baseConfig.ExposePort(6000, PortType.tcp);
  254. baseConfig.ExposePort(6010, PortType.udp);
  255. baseConfig.ExposePort(6100, PortType.udp);
  256. baseConfig.ExposePort(6200, PortType.tcp);
  257. string readyImage = baseConfig.BuildConfig();
  258. JsonNode? result = JsonNode.Parse(readyImage);
  259. var resultPorts = result?["config"]?["ExposedPorts"] as JsonObject;
  260. Assert.NotNull(resultPorts);
  261. Assert.Equal(5, resultPorts.Count);
  262. Assert.NotNull(resultPorts["6000/tcp"] as JsonObject);
  263. Assert.NotNull(resultPorts["6010/udp"] as JsonObject);
  264. Assert.NotNull(resultPorts["6100/udp"] as JsonObject);
  265. Assert.NotNull(resultPorts["6100/tcp"] as JsonObject);
  266. Assert.NotNull(resultPorts["6200/tcp"] as JsonObject);
  267. }
  268. [Fact]
  269. public void HistoryEntriesMatchNonEmptyLayers()
  270. {
  271. // Note how the base image config is already "corrupt" by having
  272. // only 5 layers with only 2 history entries. We expect the
  273. // final config to also have 5 (non empty) history entries.
  274. string simpleImageConfig =
  275. """
  276. {
  277. "architecture": "amd64",
  278. "config": {
  279. "Hostname": "",
  280. "Domainname": "",
  281. "User": "",
  282. "AttachStdin": false,
  283. "AttachStdout": false,
  284. "AttachStderr": false,
  285. "Tty": false,
  286. "OpenStdin": false,
  287. "StdinOnce": false,
  288. "Env": [
  289. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  290. "ASPNETCORE_URLS=http://+:80",
  291. "DOTNET_RUNNING_IN_CONTAINER=true",
  292. "DOTNET_VERSION=7.0.2",
  293. "ASPNET_VERSION=7.0.2"
  294. ],
  295. "Cmd": ["bash"],
  296. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  297. "Volumes": null,
  298. "WorkingDir": "",
  299. "Entrypoint": null,
  300. "OnBuild": null,
  301. "Labels": null,
  302. "ExposedPorts":
  303. {
  304. "6100/tcp": {},
  305. "6200": {}
  306. }
  307. },
  308. "created": "2023-02-04T08:14:52.000901321Z",
  309. "os": "linux",
  310. "rootfs": {
  311. "type": "layers",
  312. "diff_ids": [
  313. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  314. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  315. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  316. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  317. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  318. ]
  319. },
  320. "history": [{
  321. "created": "2023-03-01T04:09:58.669479866Z",
  322. "created_by": "/bin/sh -c #(nop) ADD file:493a5b0c8d2d63a1343258b3f9aa5fcd59a93f44fe26ad9e56b094c3a08fd3be in / "
  323. }, {
  324. "created": "2023-03-01T04:09:59.032972609Z",
  325. "created_by": "/bin/sh -c #(nop) CMD [\u0022bash\u0022]",
  326. "empty_layer": true
  327. }]
  328. }
  329. """;
  330. JsonNode? node = JsonNode.Parse(simpleImageConfig);
  331. Assert.NotNull(node);
  332. ImageConfig baseConfig = new(node);
  333. string readyImage = baseConfig.BuildConfig();
  334. JsonNode? result = JsonNode.Parse(readyImage);
  335. var historyNode = result?["history"];
  336. Assert.NotNull(historyNode);
  337. var layerDiffsNode = result?["rootfs"]?["diff_ids"];
  338. Assert.NotNull(layerDiffsNode);
  339. int nonEmptyHistoryNodes = historyNode.AsArray()
  340. .Count(h => h?.AsObject()["empty_layer"]?.GetValue<bool>() is null or false);
  341. int layerCount = layerDiffsNode.AsArray().Count;
  342. Assert.Equal(nonEmptyHistoryNodes, layerCount);
  343. }
  344. [Fact]
  345. public void CanSetUserFromAppUIDEnvVarFromBaseImage()
  346. {
  347. var expectedUid = "12345";
  348. var builder = FromBaseImageConfig($$"""
  349. {
  350. "architecture": "amd64",
  351. "config": {
  352. "Hostname": "",
  353. "Domainname": "",
  354. "User": "",
  355. "Env": [
  356. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  357. "{{ImageBuilder.EnvironmentVariables.APP_UID}}={{expectedUid}}"
  358. ],
  359. "Cmd": ["bash"],
  360. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  361. "WorkingDir": ""
  362. },
  363. "created": "2023-02-04T08:14:52.000901321Z",
  364. "os": "linux",
  365. "rootfs": {
  366. "type": "layers",
  367. "diff_ids": [
  368. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  369. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  370. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  371. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  372. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  373. ]
  374. }
  375. }
  376. """);
  377. var builtImage = builder.Build();
  378. JsonNode? result = JsonNode.Parse(builtImage.Config);
  379. Assert.NotNull(result);
  380. var assignedUid = result["config"]?["User"]?.GetValue<string>();
  381. Assert.Equal(assignedUid, expectedUid);
  382. }
  383. [Fact]
  384. public void CanSetUserFromAppUIDEnvVarFromUser()
  385. {
  386. var expectedUid = "12345";
  387. var builder = FromBaseImageConfig($$"""
  388. {
  389. "architecture": "amd64",
  390. "config": {
  391. "Hostname": "",
  392. "Domainname": "",
  393. "User": "",
  394. "Env": [
  395. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  396. ],
  397. "Cmd": ["bash"],
  398. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  399. "WorkingDir": ""
  400. },
  401. "created": "2023-02-04T08:14:52.000901321Z",
  402. "os": "linux",
  403. "rootfs": {
  404. "type": "layers",
  405. "diff_ids": [
  406. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  407. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  408. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  409. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  410. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  411. ]
  412. }
  413. }
  414. """);
  415. builder.AddEnvironmentVariable(ImageBuilder.EnvironmentVariables.APP_UID, "12345");
  416. var builtImage = builder.Build();
  417. JsonNode? result = JsonNode.Parse(builtImage.Config);
  418. Assert.NotNull(result);
  419. var assignedUser = result["config"]?["User"]?.GetValue<string>();
  420. Assert.Equal(assignedUser, expectedUid);
  421. }
  422. [InlineData("ASPNETCORE_URLS", "https://*:12345;http://+:1234;http://localhost:123;http://1.2.3.4:12", 12345, 1234, 123, 12)]
  423. [InlineData("ASPNETCORE_HTTP_PORTS", "999;666", 999, 666)]
  424. [InlineData("ASPNETCORE_HTTPS_PORTS", "456;789", 456, 789)]
  425. [Theory]
  426. public void CanSetPortFromEnvVarFromBaseImage(string envVar, string envValue, params int[] expectedPorts)
  427. {
  428. var builder = FromBaseImageConfig($$"""
  429. {
  430. "architecture": "amd64",
  431. "config": {
  432. "Hostname": "",
  433. "Domainname": "",
  434. "User": "",
  435. "Env": [
  436. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  437. "{{envVar}}={{envValue}}"
  438. ],
  439. "Cmd": ["bash"],
  440. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  441. "WorkingDir": ""
  442. },
  443. "created": "2023-02-04T08:14:52.000901321Z",
  444. "os": "linux",
  445. "rootfs": {
  446. "type": "layers",
  447. "diff_ids": [
  448. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  449. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  450. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  451. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  452. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  453. ]
  454. }
  455. }
  456. """);
  457. var builtImage = builder.Build();
  458. JsonNode? result = JsonNode.Parse(builtImage.Config);
  459. Assert.NotNull(result);
  460. var portsObject = result["config"]?["ExposedPorts"]?.AsObject();
  461. var assignedPorts = portsObject?.AsEnumerable().Select(portString => int.Parse(portString.Key.Split('/')[0])).ToArray();
  462. Assert.Equal(assignedPorts, expectedPorts);
  463. }
  464. [InlineData("ASPNETCORE_URLS", "https://*:12345;http://+:1234;http://localhost:123;http://1.2.3.4:12", 12345, 1234, 123, 12)]
  465. [InlineData("ASPNETCORE_HTTP_PORTS", "999;666", 999, 666)]
  466. [InlineData("ASPNETCORE_HTTPS_PORTS", "456;789", 456, 789)]
  467. [Theory]
  468. public void CanSetPortFromEnvVarFromUser(string envVar, string envValue, params int[] expectedPorts)
  469. {
  470. var builder = FromBaseImageConfig($$"""
  471. {
  472. "architecture": "amd64",
  473. "config": {
  474. "Hostname": "",
  475. "Domainname": "",
  476. "User": "",
  477. "Env": [
  478. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  479. ],
  480. "Cmd": ["bash"],
  481. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  482. "WorkingDir": ""
  483. },
  484. "created": "2023-02-04T08:14:52.000901321Z",
  485. "os": "linux",
  486. "rootfs": {
  487. "type": "layers",
  488. "diff_ids": [
  489. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  490. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  491. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  492. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  493. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  494. ]
  495. }
  496. }
  497. """);
  498. builder.AddEnvironmentVariable(envVar, envValue);
  499. var builtImage = builder.Build();
  500. JsonNode? result = JsonNode.Parse(builtImage.Config);
  501. Assert.NotNull(result);
  502. var portsObject = result["config"]?["ExposedPorts"]?.AsObject();
  503. var assignedPorts = portsObject?.AsEnumerable().Select(portString => int.Parse(portString.Key.Split('/')[0])).ToArray();
  504. Assert.Equal(assignedPorts, expectedPorts);
  505. }
  506. [Fact]
  507. public void CanSetContainerUserAndOverrideAppUID()
  508. {
  509. var userId = "1646";
  510. var baseConfigBuilder = FromBaseImageConfig($$"""
  511. {
  512. "architecture": "amd64",
  513. "config": {
  514. "Hostname": "",
  515. "Domainname": "",
  516. "User": "",
  517. "Env": [
  518. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  519. "{{ImageBuilder.EnvironmentVariables.APP_UID}}=12345"
  520. ],
  521. "Cmd": ["bash"],
  522. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  523. "WorkingDir": ""
  524. },
  525. "created": "2023-02-04T08:14:52.000901321Z",
  526. "os": "linux",
  527. "rootfs": {
  528. "type": "layers",
  529. "diff_ids": [
  530. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  531. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  532. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  533. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  534. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  535. ]
  536. }
  537. }
  538. """);
  539. baseConfigBuilder.SetUser(userId);
  540. var config = JsonNode.Parse(baseConfigBuilder.Build().Config);
  541. config!["config"]?["User"]?.GetValue<string>().Should().Be(expected: userId, because: "The precedence of SetUser should override inferred user ids");
  542. }
  543. [Fact]
  544. public void WhenMultipleUrlSourcesAreSetOnlyAspnetcoreUrlsIsUsed()
  545. {
  546. int[] expected = [12345];
  547. var builder = FromBaseImageConfig($$"""
  548. {
  549. "architecture": "amd64",
  550. "config": {
  551. "Hostname": "",
  552. "Domainname": "",
  553. "User": "",
  554. "Env": [
  555. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  556. ],
  557. "Cmd": ["bash"],
  558. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  559. "WorkingDir": ""
  560. },
  561. "created": "2023-02-04T08:14:52.000901321Z",
  562. "os": "linux",
  563. "rootfs": {
  564. "type": "layers",
  565. "diff_ids": [
  566. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  567. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  568. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  569. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  570. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  571. ]
  572. }
  573. }
  574. """);
  575. builder.AddEnvironmentVariable(ImageBuilder.EnvironmentVariables.ASPNETCORE_URLS, "https://*:12345");
  576. builder.AddEnvironmentVariable(ImageBuilder.EnvironmentVariables.ASPNETCORE_HTTPS_PORTS, "456");
  577. var builtImage = builder.Build();
  578. JsonNode? result = JsonNode.Parse(builtImage.Config);
  579. Assert.NotNull(result);
  580. var portsObject = result["config"]?["ExposedPorts"]?.AsObject();
  581. var assignedPorts = portsObject?.AsEnumerable().Select(portString => int.Parse(portString.Key.Split('/')[0])).ToArray();
  582. Assert.Equal(expected, assignedPorts);
  583. }
  584. [Fact]
  585. public void CanSetBaseImageDigestLabel()
  586. {
  587. var builder = FromBaseImageConfig($$"""
  588. {
  589. "architecture": "amd64",
  590. "config": {
  591. "Hostname": "",
  592. "Domainname": "",
  593. "User": "",
  594. "Env": [
  595. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  596. ],
  597. "Cmd": ["bash"],
  598. "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69",
  599. "WorkingDir": ""
  600. },
  601. "created": "2023-02-04T08:14:52.000901321Z",
  602. "os": "linux",
  603. "rootfs": {
  604. "type": "layers",
  605. "diff_ids": [
  606. "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf",
  607. "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085",
  608. "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d",
  609. "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3",
  610. "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48"
  611. ]
  612. }
  613. }
  614. """);
  615. builder.AddBaseImageDigestLabel();
  616. var builtImage = builder.Build();
  617. JsonNode? result = JsonNode.Parse(builtImage.Config);
  618. Assert.NotNull(result);
  619. var labels = result["config"]?["Labels"]?.AsObject();
  620. var digest = labels?.AsEnumerable().First(label => label.Key == "org.opencontainers.image.base.digest").Value!;
  621. digest.GetValue<string>().Should().Be(StaticKnownDigestValue);
  622. }
  623. private ImageBuilder FromBaseImageConfig(string baseImageConfig, [CallerMemberName] string testName = "")
  624. {
  625. var manifest = new ManifestV2()
  626. {
  627. SchemaVersion = 2,
  628. MediaType = SchemaTypes.DockerManifestV2,
  629. Config = new ManifestConfig()
  630. {
  631. mediaType = "",
  632. size = 0,
  633. digest = "sha256:"
  634. },
  635. Layers = new List<ManifestLayer>(),
  636. KnownDigest = StaticKnownDigestValue
  637. };
  638. return new ImageBuilder(manifest, new ImageConfig(baseImageConfig), _loggerFactory.CreateLogger(testName));
  639. }
  640. }