Bump docker/cli v23.0.10

pull/515/head
ian 3 months ago
parent 2fd3dabf69
commit 6fb219cd5f

@ -1,69 +1,6 @@
mode: atomic
github.com/jesseduffield/lazydocker/pkg/app/app.go:30.53,39.16 5 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:39.16,41.3 1 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:42.2,47.16 3 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:47.16,49.3 1 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:50.2,52.16 3 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:52.16,54.3 1 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:55.2,55.17 1 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:58.29,60.2 1 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:62.31,64.2 1 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:72.54,82.35 3 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:82.35,83.60 1 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:83.60,85.4 1 0
github.com/jesseduffield/lazydocker/pkg/app/app.go:88.2,88.18 1 0
mode: atomic
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:31.17,33.2 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:35.32,37.16 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:37.16,38.13 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:41.2,41.46 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:41.46,47.17 5 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:47.17,48.14 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:51.3,59.29 4 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:63.45,65.16 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:65.16,67.3 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:70.39,72.2 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:74.49,76.2 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:78.58,81.59 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:81.59,82.32 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:82.32,83.12 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:86.3,87.21 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:87.21,89.4 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:91.3,102.77 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:105.2,105.24 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:108.106,109.31 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:109.31,111.3 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:113.2,113.42 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:113.42,114.29 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:114.29,117.4 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:120.2,125.41 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:128.78,131.42 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:131.42,134.44 3 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:134.44,136.4 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:137.3,137.24 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/generate.go:140.2,140.16 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:15.14,20.16 4 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:20.16,22.3 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:23.2,25.47 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:25.47,27.3 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:29.2,34.27 4 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:34.27,36.3 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:38.2,38.38 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:38.38,47.18 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:47.18,49.4 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:50.3,56.13 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:59.2,59.45 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:62.33,64.2 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:66.39,70.81 3 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:70.81,71.27 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:71.27,73.18 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:73.18,75.5 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:76.4,77.28 2 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:80.3,80.13 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:82.2,82.16 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:82.16,84.3 1 0
github.com/jesseduffield/lazydocker/pkg/cheatsheet/validate.go:86.2,86.16 1 0
mode: atomic
github.com/jesseduffield/lazydocker/pkg/commands/container.go:46.78,48.86 2 0
github.com/jesseduffield/lazydocker/pkg/commands/container.go:48.86,49.100 1 0
@ -1659,173 +1596,10 @@ github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:265.47,266
github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:266.22,268.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:270.2,270.20 1 0
mode: atomic
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:20.111,22.9 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:22.9,24.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:26.2,28.34 3 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:28.34,30.17 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:30.17,32.4 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:33.3,33.78 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:36.2,41.16 5 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:41.16,43.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:45.2,53.22 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:57.99,63.46 4 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:63.46,65.17 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:65.17,67.4 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:68.3,69.17 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:69.17,71.4 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:73.3,73.23 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:76.2,77.24 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:77.24,79.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:81.2,82.24 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:82.24,84.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:86.2,87.21 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:87.21,89.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:91.2,105.8 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:109.49,113.25 3 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:114.15,115.16 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:116.15,117.25 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:118.13,119.25 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:120.13,121.25 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:122.11,123.25 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:124.14,125.25 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:126.14,127.25 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:128.12,129.25 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:130.14,131.35 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:132.10,135.40 3 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:135.40,138.4 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:138.9,138.48 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:138.48,142.4 3 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/container_stats.go:142.9,144.4 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:17.102,27.2 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:29.66,31.2 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:33.49,34.85 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:34.85,35.27 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:35.27,37.4 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:42.3,43.27 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:43.27,45.4 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:46.3,46.92 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:51.2,53.40 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:57.91,79.46 4 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:80.15,81.53 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:82.14,83.60 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:84.14,85.14 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:86.10,87.37 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:90.2,90.66 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:94.94,95.24 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:95.24,97.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:99.2,99.27 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:100.16,103.4 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:104.17,105.39 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:106.10,107.12 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:111.81,112.24 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:112.24,114.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:116.2,122.35 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:122.35,124.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:126.2,139.46 4 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:140.15,141.69 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:142.14,143.76 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:144.14,145.14 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:146.10,147.47 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:150.2,150.86 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:150.86,152.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:153.2,153.11 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:157.54,159.9 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:159.9,161.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:163.2,167.21 4 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:167.21,169.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:169.8,169.28 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:169.28,171.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:171.8,173.3 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:175.2,175.54 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:179.63,180.27 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:181.16,184.58 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:184.58,186.4 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:187.3,187.21 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:188.17,189.22 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:190.17,191.23 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:192.16,193.24 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:194.14,195.21 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:196.20,197.22 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:198.18,199.25 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/containers.go:200.10,201.23 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/images.go:8.61,14.2 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/menu_items.go:5.67,7.2 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/networks.go:5.67,7.2 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/projects.go:5.67,7.2 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/services.go:10.96,11.30 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/services.go:11.30,13.47 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/services.go:14.16,15.24 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/services.go:16.15,17.24 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/services.go:18.15,19.15 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/services.go:20.11,21.27 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/services.go:24.3,31.4 1 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/services.go:34.2,42.3 2 0
github.com/jesseduffield/lazydocker/pkg/gui/presentation/volumes.go:5.64,7.2 1 0
mode: atomic
mode: atomic
github.com/jesseduffield/lazydocker/pkg/i18n/chinese.go:3.34,136.2 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/dutch.go:3.32,98.2 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/english.go:133.34,267.2 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/french.go:3.33,120.2 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/german.go:3.33,97.2 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:19.101,20.30 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:20.30,23.3 2 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:25.2,25.40 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:25.40,26.28 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:26.28,28.4 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:31.2,31.90 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:34.76,39.65 3 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:39.65,40.48 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:40.48,42.4 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:45.2,45.17 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:49.53,61.2 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:64.65,65.49 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:65.49,67.3 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/i18n.go:69.2,69.12 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/polish.go:3.33,97.2 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/portuguese.go:3.37,137.2 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/spanish.go:3.34,129.2 1 0
github.com/jesseduffield/lazydocker/pkg/i18n/turkish.go:3.34,97.2 1 0
mode: atomic
github.com/jesseduffield/lazydocker/pkg/log/log.go:14.76,16.50 2 0
github.com/jesseduffield/lazydocker/pkg/log/log.go:16.50,18.3 1 0
github.com/jesseduffield/lazydocker/pkg/log/log.go:18.8,20.3 1 0
github.com/jesseduffield/lazydocker/pkg/log/log.go:24.2,31.4 2 0
github.com/jesseduffield/lazydocker/pkg/log/log.go:34.33,37.16 3 0
github.com/jesseduffield/lazydocker/pkg/log/log.go:37.16,39.3 1 0
github.com/jesseduffield/lazydocker/pkg/log/log.go:40.2,40.14 1 0
github.com/jesseduffield/lazydocker/pkg/log/log.go:43.68,47.16 4 0
github.com/jesseduffield/lazydocker/pkg/log/log.go:47.16,50.3 2 0
github.com/jesseduffield/lazydocker/pkg/log/log.go:51.2,52.12 2 0
github.com/jesseduffield/lazydocker/pkg/log/log.go:55.43,60.2 4 0
mode: atomic
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:34.90,36.2 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:39.31,40.26 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:40.26,42.3 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:44.2,46.12 2 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:46.12,49.3 2 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:51.2,51.9 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:52.11,53.9 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:54.37,55.41 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:59.66,60.12 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:60.12,68.27 7 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:68.27,70.4 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:72.3,75.27 3 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:75.27,79.4 3 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:81.3,89.13 2 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:89.13,93.4 3 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:96.2,96.12 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:99.23,102.15 3 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:102.15,104.3 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:106.2,110.18 5 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:116.159,119.45 2 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:119.45,120.20 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:120.20,122.4 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:123.3,127.7 4 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:127.7,128.11 1 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:129.25,131.11 2 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:132.22,134.11 2 0
github.com/jesseduffield/lazydocker/pkg/tasks/tasks.go:135.22,137.26 2 0
mode: atomic
github.com/jesseduffield/lazydocker/pkg/utils/utils.go:30.50,32.54 2 3
github.com/jesseduffield/lazydocker/pkg/utils/utils.go:32.54,34.3 1 2

@ -6,7 +6,7 @@ require (
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/docker/cli v20.10.15+incompatible
github.com/docker/cli v23.0.10-0.20240125104041-672b1497b973+incompatible
github.com/docker/docker v23.0.10-0.20240216160024-548f37a132d9+incompatible
github.com/fatih/color v1.10.0
github.com/go-errors/errors v1.4.2

@ -12,8 +12,8 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/cli v20.10.15+incompatible h1:HGO75iIgpyuG1m0hw0Kp7hY5o7XELmY1rsT9gIptOSU=
github.com/docker/cli v20.10.15+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v23.0.10-0.20240125104041-672b1497b973+incompatible h1:2be3Bu00Kwz9D7RNj2wwQWK+XAT9IYg9Ifvz/CDQ714=
github.com/docker/cli v23.0.10-0.20240125104041-672b1497b973+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v23.0.10-0.20240216160024-548f37a132d9+incompatible h1:C4b6PgP3QHSikEWaQ2Q8LFBfMHCEC9QiQ2hZ/If/Vrk=

@ -1,9 +1,10 @@
# This file lists all individuals having contributed content to the repository.
# For how it is generated, see `scripts/docs/generate-authors.sh`.
# File @generated by scripts/docs/generate-authors.sh. DO NOT EDIT.
# This file lists all contributors to the repository.
# See scripts/docs/generate-authors.sh to make modifications.
Aanand Prasad <aanand.prasad@gmail.com>
Aaron L. Xu <liker.xu@foxmail.com>
Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Lehmann <alehmann@netflix.com>
Aaron.L.Xu <likexu@harmonycloud.cn>
Abdur Rehman <abdur_rehman@mentor.com>
Abhinandan Prativadi <abhi@docker.com>
@ -24,22 +25,27 @@ Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Akim Demaille <akim.demaille@docker.com>
Alan Thompson <cloojure@gmail.com>
Albert Callarisa <shark234@gmail.com>
Alberto Roura <mail@albertoroura.com>
Albin Kerouanton <albin@akerouanton.name>
Aleksa Sarai <asarai@suse.de>
Aleksander Piotrowski <apiotrowski312@gmail.com>
Alessandro Boch <aboch@tetrationanalytics.com>
Alex Couture-Beil <alex@earthly.dev>
Alex Mavrogiannis <alex.mavrogiannis@docker.com>
Alex Mayer <amayer5125@gmail.com>
Alexander Boyd <alex@opengroove.org>
Alexander Larsson <alexl@redhat.com>
Alexander Morozov <lk4d4@docker.com>
Alexander Morozov <lk4d4math@gmail.com>
Alexander Ryabov <i@sepa.spb.ru>
Alexandre González <agonzalezro@gmail.com>
Alexey Igrychev <alexey.igrychev@flant.com>
Alexis Couvreur <alexiscouvreur.pro@gmail.com>
Alfred Landrum <alfred.landrum@docker.com>
Alicia Lauerman <alicia@eta.im>
Allen Sun <allensun.shl@alibaba-inc.com>
Alvin Deng <alvin.q.deng@utexas.edu>
Amen Belayneh <amenbelayneh@gmail.com>
Amey Shrivastava <72866602+AmeyShrivastava@users.noreply.github.com>
Amir Goldstein <amir73il@aquasec.com>
Amit Krishnan <amit.krishnan@oracle.com>
Amit Shukla <amit.shukla@docker.com>
@ -48,6 +54,8 @@ Anca Iordache <anca.iordache@docker.com>
Anda Xu <anda.xu@docker.com>
Andrea Luzzardi <aluzzardi@gmail.com>
Andreas Köhler <andi5.py@gmx.net>
Andres G. Aragoneses <knocte@gmail.com>
Andres Leon Rangel <aleon1220@gmail.com>
Andrew France <andrew@avito.co.uk>
Andrew Hsu <andrewhsu@docker.com>
Andrew Macpherson <hopscotch23@gmail.com>
@ -67,8 +75,9 @@ Antonis Kalipetis <akalipetis@gmail.com>
Anusha Ragunathan <anusha.ragunathan@docker.com>
Ao Li <la9249@163.com>
Arash Deshmeh <adeshmeh@ca.ibm.com>
Arko Dasgupta <arko.dasgupta@docker.com>
Arnaud Porterie <arnaud.porterie@docker.com>
Arko Dasgupta <arko@tetrate.io>
Arnaud Porterie <icecrime@gmail.com>
Arnaud Rebillout <elboulangero@gmail.com>
Arthur Peka <arthur.peka@outlook.com>
Ashwini Oruganti <ashwini.oruganti@gmail.com>
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
@ -76,18 +85,23 @@ Bardia Keyoumarsi <bkeyouma@ucsc.edu>
Barnaby Gray <barnaby@pickle.me.uk>
Bastiaan Bakker <bbakker@xebia.com>
BastianHofmann <bastianhofmann@me.com>
Ben Bodenmiller <bbodenmiller@gmail.com>
Ben Bonnefoy <frenchben@docker.com>
Ben Creasy <ben@bencreasy.com>
Ben Firshman <ben@firshman.co.uk>
Benjamin Boudreau <boudreau.benjamin@gmail.com>
Benjamin Böhmke <benjamin@boehmke.net>
Benjamin Nater <me@bn4t.me>
Benoit Sigoure <tsunanet@gmail.com>
Bhumika Bayani <bhumikabayani@gmail.com>
Bill Wang <ozbillwang@gmail.com>
Bin Liu <liubin0329@gmail.com>
Bingshen Wang <bingshen.wbs@alibaba-inc.com>
Bishal Das <bishalhnj127@gmail.com>
Boaz Shuster <ripcurld.github@gmail.com>
Bogdan Anton <contact@bogdananton.ro>
Boris Pruessmann <boris@pruessmann.org>
Brad Baker <brad@brad.fi>
Bradley Cicenas <bradley.cicenas@gmail.com>
Brandon Mitchell <git@bmitch.net>
Brandon Philips <brandon.philips@coreos.com>
@ -96,6 +110,7 @@ Bret Fisher <bret@bretfisher.com>
Brian (bex) Exelbierd <bexelbie@redhat.com>
Brian Goff <cpuguy83@gmail.com>
Brian Wieder <brian@4wieders.com>
Bruno Sousa <bruno.sousa@docker.com>
Bryan Bess <squarejaw@bsbess.com>
Bryan Boreham <bjboreham@gmail.com>
Bryan Murphy <bmurphy1976@gmail.com>
@ -114,15 +129,19 @@ Charles Chan <charleswhchan@users.noreply.github.com>
Charles Law <claw@conduce.com>
Charles Smith <charles.smith@docker.com>
Charlie Drage <charlie@charliedrage.com>
Charlotte Mach <charlotte.mach@fs.lmu.de>
ChaYoung You <yousbe@gmail.com>
Chee Hau Lim <cheehau.lim@mobimeo.com>
Chen Chuanliang <chen.chuanliang@zte.com.cn>
Chen Hanxiao <chenhanxiao@cn.fujitsu.com>
Chen Mingjie <chenmingjie0828@163.com>
Chen Qiu <cheney-90@hotmail.com>
Chris Couzens <ccouzens@gmail.com>
Chris Gavin <chris@chrisgavin.me>
Chris Gibson <chris@chrisg.io>
Chris McKinnel <chrismckinnel@gmail.com>
Chris Snow <chsnow123@gmail.com>
Chris Vermilion <christopher.vermilion@gmail.com>
Chris Weyl <cweyl@alumni.drew.edu>
Christian Persson <saser@live.se>
Christian Stefanescu <st.chris@gmail.com>
@ -131,6 +150,7 @@ Christophe Vidal <kriss@krizalys.com>
Christopher Biscardi <biscarch@sketcht.com>
Christopher Crone <christopher.crone@docker.com>
Christopher Jones <tophj@linux.vnet.ibm.com>
Christopher Svensson <stoffus@stoffus.com>
Christy Norman <christy@linux.vnet.ibm.com>
Chun Chen <ramichen@tencent.com>
Clinton Kitson <clintonskitson@gmail.com>
@ -139,8 +159,10 @@ Colin Hebert <hebert.colin@gmail.com>
Collin Guarino <collin.guarino@gmail.com>
Colm Hally <colmhally@gmail.com>
Comical Derskeal <27731088+derskeal@users.noreply.github.com>
Conner Crosby <conner@cavcrosby.tech>
Corey Farrell <git@cfware.com>
Corey Quon <corey.quon@docker.com>
Cory Bennet <cbennett@netflix.com>
Craig Wilhite <crwilhit@microsoft.com>
Cristian Staretu <cristian.staretu@gmail.com>
Daehyeok Mun <daehyeok@gmail.com>
@ -170,11 +192,13 @@ Dattatraya Kumbhar <dattatraya.kumbhar@gslab.com>
Dave Goodchild <buddhamagnet@gmail.com>
Dave Henderson <dhenderson@gmail.com>
Dave Tucker <dt@docker.com>
David Alvarez <david.alvarez@flyeralarm.com>
David Beitey <david@davidjb.com>
David Calavera <david.calavera@gmail.com>
David Cramer <davcrame@cisco.com>
David Dooling <dooling@gmail.com>
David Gageot <david@gageot.net>
David Karlsson <david.karlsson@docker.com>
David Lechner <david@lechnology.com>
David Scott <dave@recoil.org>
David Sheets <dsheets@docker.com>
@ -186,7 +210,8 @@ Denis Defreyne <denis@soundcloud.com>
Denis Gladkikh <denis@gladkikh.email>
Denis Ollier <larchunix@users.noreply.github.com>
Dennis Docter <dennis@d23.nl>
Derek McGowan <derek@mcgstyle.net>
Derek McGowan <derek@mcg.dev>
Des Preston <despreston@gmail.com>
Deshi Xiao <dxiao@redhat.com>
Dharmit Shah <shahdharmit@gmail.com>
Dhawal Yogesh Bhanushali <dbhanushali@vmware.com>
@ -196,12 +221,14 @@ Dimitry Andric <d.andric@activevideo.com>
Ding Fei <dingfei@stars.org.cn>
Diogo Monica <diogo@docker.com>
Djordje Lukic <djordje.lukic@docker.com>
Dmitriy Fishman <fishman.code@gmail.com>
Dmitry Gusev <dmitry.gusev@gmail.com>
Dmitry Smirnov <onlyjob@member.fsf.org>
Dmitry V. Krivenok <krivenok.dmitry@gmail.com>
Dominik Braun <dominik.braun@nbsp.de>
Don Kjer <don.kjer@gmail.com>
Dong Chen <dongluo.chen@docker.com>
DongGeon Lee <secmatth1996@gmail.com>
Doug Davis <dug@us.ibm.com>
Drew Erny <derny@mirantis.com>
Ed Costello <epc@epcostello.com>
@ -211,12 +238,14 @@ Eli Uriegas <seemethere101@gmail.com>
Elias Faxö <elias.faxo@tre.se>
Elliot Luo <956941328@qq.com>
Eric Curtin <ericcurtin17@gmail.com>
Eric Engestrom <eric@engestrom.ch>
Eric G. Noriega <enoriega@vizuri.com>
Eric Rosenberg <ehaydenr@gmail.com>
Eric Sage <eric.david.sage@gmail.com>
Eric-Olivier Lamey <eo@lamey.me>
Erica Windisch <erica@windisch.us>
Erik Hollensbe <github@hollensbe.org>
Erik Humphrey <erik.humphrey@carleton.ca>
Erik St. Martin <alakriti@gmail.com>
Essam A. Hassan <es.hassan187@gmail.com>
Ethan Haynes <ethanhaynes@alumni.harvard.edu>
@ -229,8 +258,10 @@ Evelyn Xu <evelynhsu21@gmail.com>
Everett Toews <everett.toews@rackspace.com>
Fabio Falci <fabiofalci@gmail.com>
Fabrizio Soppelsa <fsoppelsa@mirantis.com>
Felix Geyer <debfx@fobos.de>
Felix Hupfeld <felix@quobyte.com>
Felix Rabe <felix@rabe.io>
fezzik1620 <fezzik1620@users.noreply.github.com>
Filip Jareš <filipjares@gmail.com>
Flavio Crisciani <flavio.crisciani@docker.com>
Florian Klein <florian.klein@free.fr>
@ -242,6 +273,7 @@ Frederic Hemberger <mail@frederic-hemberger.de>
Frederick F. Kautz IV <fkautz@redhat.com>
Frederik Nordahl Jul Sabroe <frederikns@gmail.com>
Frieder Bluemle <frieder.bluemle@gmail.com>
Gabriel Gore <gabgore@cisco.com>
Gabriel Nicolas Avellaneda <avellaneda.gabriel@gmail.com>
Gaetan de Villele <gdevillele@gmail.com>
Gang Qiao <qiaohai8866@gmail.com>
@ -251,13 +283,18 @@ George MacRorie <gmacr31@gmail.com>
George Xie <georgexsh@gmail.com>
Gianluca Borello <g.borello@gmail.com>
Gildas Cuisinier <gildas.cuisinier@gcuisinier.net>
Gio d'Amelio <giodamelio@gmail.com>
Gleb Stsenov <gleb.stsenov@gmail.com>
Goksu Toprak <goksu.toprak@docker.com>
Gou Rao <gou@portworx.com>
Govind Rai <raigovind93@gmail.com>
Grant Reaber <grant.reaber@gmail.com>
Greg Pflaum <gpflaum@users.noreply.github.com>
Gsealy <jiaojingwei1001@hotmail.com>
Guilhem Lettron <guilhem+github@lettron.fr>
Guillaume J. Charmes <guillaume.charmes@docker.com>
Guillaume Le Floch <glfloch@gmail.com>
Guillaume Tardif <guillaume.tardif@gmail.com>
gwx296173 <gaojing3@huawei.com>
Günther Jungbluth <gunther@gameslabs.net>
Hakan Özler <hakan.ozler@kodcu.com>
@ -278,6 +315,7 @@ Hugo Gabriel Eyherabide <hugogabriel.eyherabide@gmail.com>
huqun <huqun@zju.edu.cn>
Huu Nguyen <huu@prismskylabs.com>
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com>
Iain Samuel McLean Elder <iain@isme.es>
Ian Campbell <ian.campbell@docker.com>
Ian Philpot <ian.philpot@microsoft.com>
Ignacio Capurro <icapurrofagian@gmail.com>
@ -287,6 +325,7 @@ Ilya Sotkov <ilya@sotkov.com>
Ioan Eugen Stan <eu@ieugen.ro>
Isabel Jimenez <contact.isabeljimenez@gmail.com>
Ivan Grcic <igrcic@gmail.com>
Ivan Grund <ivan.grund@gmail.com>
Ivan Markin <sw@nogoegst.net>
Jacob Atzen <jacob@jacobatzen.dk>
Jacob Tomlinson <jacob@tom.linson.uk>
@ -302,15 +341,18 @@ Jan-Jaap Driessen <janjaapdriessen@gmail.com>
Jana Radhakrishnan <mrjana@docker.com>
Jared Hocutt <jaredh@netapp.com>
Jasmine Hegman <jasmine@jhegman.com>
Jason Hall <jason@chainguard.dev>
Jason Heiss <jheiss@aput.net>
Jason Plum <jplum@devonit.com>
Jay Kamat <github@jgkamat.33mail.com>
Jean Lecordier <jeanlecordier@hotmail.fr>
Jean Rouge <rougej+github@gmail.com>
Jean-Christophe Sirot <jean-christophe.sirot@docker.com>
Jean-Pierre Huynh <jean-pierre.huynh@ounet.fr>
Jeff Lindsay <progrium@gmail.com>
Jeff Nickoloff <jeff.nickoloff@gmail.com>
Jeff Silberman <jsilberm@gmail.com>
Jennings Zhang <jenni_zh@protonmail.com>
Jeremy Chambers <jeremy@thehipbot.com>
Jeremy Unruh <jeremybunruh@gmail.com>
Jeremy Yallop <yallop@docker.com>
@ -322,6 +364,7 @@ Jian Zhang <zhangjian.fnst@cn.fujitsu.com>
Jie Luo <luo612@zju.edu.cn>
Jilles Oldenbeuving <ojilles@gmail.com>
Jim Galasyn <jim.galasyn@docker.com>
Jim Lin <b04705003@ntu.edu.tw>
Jimmy Leger <jimmy.leger@gmail.com>
Jimmy Song <rootsongjc@gmail.com>
jimmyxian <jimmyxian2004@yahoo.com.cn>
@ -338,6 +381,7 @@ Johannes 'fish' Ziemke <github@freigeist.org>
John Feminella <jxf@jxf.me>
John Harris <john@johnharris.io>
John Howard <github@lowenna.com>
John Howard <howardjohn@google.com>
John Laswell <john.n.laswell@gmail.com>
John Maguire <jmaguire@duosecurity.com>
John Mulhausen <john@docker.com>
@ -347,13 +391,16 @@ John Tims <john.k.tims@gmail.com>
John V. Martinez <jvmatl@gmail.com>
John Willis <john.willis@docker.com>
Jon Johnson <jonjohnson@google.com>
Jon Zeolla <zeolla@gmail.com>
Jonatas Baldin <jonatas.baldin@gmail.com>
Jonathan Boulle <jonathanboulle@gmail.com>
Jonathan Lee <jonjohn1232009@gmail.com>
Jonathan Lomas <jonathan@floatinglomas.ca>
Jonathan McCrohan <jmccrohan@gmail.com>
Jonathan Warriss-Simmons <misterws@diogenes.ws>
Jonh Wendell <jonh.wendell@redhat.com>
Jordan Jennings <jjn2009@gmail.com>
Jorge Vallecillo <jorgevallecilloc@gmail.com>
Jose J. Escobar <53836904+jescobar-docker@users.noreply.github.com>
Joseph Kern <jkern@semafour.net>
Josh Bodah <jb3689@yahoo.com>
@ -383,9 +430,11 @@ Katie McLaughlin <katie@glasnt.com>
Ke Xu <leonhartx.k@gmail.com>
Kei Ohmura <ohmura.kei@gmail.com>
Keith Hudgins <greenman@greenman.org>
Kelton Bassingthwaite <KeltonBassingthwaite@gmail.com>
Ken Cochrane <kencochrane@gmail.com>
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
Kevin Alvarez <crazy-max@users.noreply.github.com>
Kevin Burke <kev@inburke.com>
Kevin Feyrer <kevin.feyrer@btinternet.com>
Kevin Kern <kaiwentan@harmonycloud.cn>
@ -401,6 +450,7 @@ Krasi Georgiev <krasi@vip-consult.solutions>
Kris-Mikael Krister <krismikael@protonmail.com>
Kun Zhang <zkazure@gmail.com>
Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
Kyle Mitofsky <Kylemit@gmail.com>
Lachlan Cooper <lachlancooper@gmail.com>
Lai Jiangshan <jiangshanlai@gmail.com>
Lars Kellogg-Stedman <lars@redhat.com>
@ -410,6 +460,7 @@ Lee Gaines <eightlimbed@gmail.com>
Lei Jitang <leijitang@huawei.com>
Lennie <github@consolejunkie.net>
Leo Gallucci <elgalu3@gmail.com>
Leonid Skorospelov <leosko94@gmail.com>
Lewis Daly <lewisdaly@me.com>
Li Yi <denverdino@gmail.com>
Li Yi <weiyuan.yl@alibaba-inc.com>
@ -445,6 +496,7 @@ Manjunath A Kumatagi <mkumatag@in.ibm.com>
Mansi Nahar <mmn4185@rit.edu>
mapk0y <mapk0y@gmail.com>
Marc Bihlmaier <marc.bihlmaier@reddoxx.com>
Marc Cornellà <hello@mcornella.com>
Marco Mariani <marco.mariani@alterway.fr>
Marco Vedovati <mvedovati@suse.com>
Marcus Martins <marcus@docker.com>
@ -459,6 +511,7 @@ Mason Fish <mason.fish@docker.com>
Mason Malone <mason.malone@gmail.com>
Mateusz Major <apkd@users.noreply.github.com>
Mathieu Champlon <mathieu.champlon@docker.com>
Mathieu Rollet <matletix@gmail.com>
Matt Gucci <matt9ucci@gmail.com>
Matt Robenolt <matt@ydekproductions.com>
Matteo Orefice <matteo.orefice@bites4bits.software>
@ -467,11 +520,13 @@ Matthieu Hauglustaine <matt.hauglustaine@gmail.com>
Mauro Porras P <mauroporrasp@gmail.com>
Max Shytikov <mshytikov@gmail.com>
Maxime Petazzoni <max@signalfuse.com>
Maximillian Fan Xavier <maximillianfx@gmail.com>
Mei ChunTao <mei.chuntao@zte.com.cn>
Metal <2466052+tedhexaflow@users.noreply.github.com>
Micah Zoltu <micah@newrelic.com>
Michael A. Smith <michael@smith-li.com>
Michael Bridgen <mikeb@squaremobius.net>
Michael Crosby <michael@docker.com>
Michael Crosby <crosbymichael@gmail.com>
Michael Friis <friism@gmail.com>
Michael Irwin <mikesir87@gmail.com>
Michael Käufl <docker@c.michael-kaeufl.de>
@ -487,6 +542,7 @@ Mihai Borobocea <MihaiBorob@gmail.com>
Mihuleacc Sergiu <mihuleac.sergiu@gmail.com>
Mike Brown <brownwm@us.ibm.com>
Mike Casas <mkcsas0@gmail.com>
Mike Dalton <mikedalton@github.com>
Mike Danese <mikedanese@google.com>
Mike Dillon <mike@embody.org>
Mike Goelzer <mike.goelzer@docker.com>
@ -503,9 +559,12 @@ Mohini Anne Dsouza <mohini3917@gmail.com>
Moorthy RS <rsmoorthy@gmail.com>
Morgan Bauer <mbauer@us.ibm.com>
Morten Hekkvang <morten.hekkvang@sbab.se>
Morten Linderud <morten@linderud.pw>
Moysés Borges <moysesb@gmail.com>
Mozi <29089388+pzhlkj6612@users.noreply.github.com>
Mrunal Patel <mrunalp@gmail.com>
muicoder <muicoder@gmail.com>
Murukesh Mohanan <murukesh.mohanan@gmail.com>
Muthukumar R <muthur@gmail.com>
Máximo Cuadros <mcuadros@gmail.com>
Mårten Cassel <marten.cassel@gmail.com>
@ -521,6 +580,7 @@ Nathan LeClaire <nathan.leclaire@docker.com>
Nathan McCauley <nathan.mccauley@docker.com>
Neil Peterson <neilpeterson@outlook.com>
Nick Adcock <nick.adcock@docker.com>
Nick Santos <nick.santos@docker.com>
Nico Stapelbroek <nstapelbroek@gmail.com>
Nicola Kabar <nicolaka@gmail.com>
Nicolas Borboën <ponsfrilus@gmail.com>
@ -535,6 +595,8 @@ Noah Treuhaft <noah.treuhaft@docker.com>
O.S. Tezer <ostezer@gmail.com>
Odin Ugedal <odin@ugedal.com>
ohmystack <jun.jiang02@ele.me>
OKA Naoya <git@okanaoya.com>
Oliver Pomeroy <oppomeroy@gmail.com>
Olle Jonsson <olle.jonsson@gmail.com>
Olli Janatuinen <olli.janatuinen@gmail.com>
Oscar Wieman <oscrx@icloud.com>
@ -550,9 +612,12 @@ Paul Lietar <paul@lietar.net>
Paul Mulders <justinkb@gmail.com>
Paul Weaver <pauweave@cisco.com>
Pavel Pospisil <pospispa@gmail.com>
Paweł Gronowski <pawel.gronowski@docker.com>
Paweł Pokrywka <pepawel@users.noreply.github.com>
Paweł Szczekutowicz <pszczekutowicz@gmail.com>
Peeyush Gupta <gpeeyush@linux.vnet.ibm.com>
Per Lundberg <per.lundberg@ecraft.com>
Peter Dave Hello <hsu@peterdavehello.org>
Peter Edge <peter.edge@gmail.com>
Peter Hsu <shhsu@microsoft.com>
Peter Jaffe <pjaffe@nevo.com>
@ -560,11 +625,13 @@ Peter Kehl <peter.kehl@gmail.com>
Peter Nagy <xificurC@gmail.com>
Peter Salvatore <peter@psftw.com>
Peter Waller <p@pwaller.net>
Phil Estes <estesp@linux.vnet.ibm.com>
Phil Estes <estesp@gmail.com>
Philip Alexander Etling <paetling@gmail.com>
Philipp Gillé <philipp.gille@gmail.com>
Philipp Schmied <pschmied@schutzwerk.com>
Phong Tran <tran.pho@northeastern.edu>
pidster <pid@pidster.com>
Pieter E Smit <diepes@github.com>
pixelistik <pixelistik@users.noreply.github.com>
Pratik Karki <prertik@outlook.com>
Prayag Verma <prayag.verma@gmail.com>
@ -574,6 +641,7 @@ Qiang Huang <h.huangqiang@huawei.com>
Qinglan Peng <qinglanpeng@zju.edu.cn>
qudongfang <qudongfang@gmail.com>
Raghavendra K T <raghavendra.kt@linux.vnet.ibm.com>
Rahul Kadyan <hi@znck.me>
Rahul Zoldyck <rahulzoldyck@gmail.com>
Ravi Shekhar Jethani <rsjethani@gmail.com>
Ray Tsang <rayt@google.com>
@ -582,6 +650,7 @@ Remy Suen <remy.suen@gmail.com>
Renaud Gaubert <rgaubert@nvidia.com>
Ricardo N Feliciano <FelicianoTech@gmail.com>
Rich Moyse <rich@moyse.us>
Richard Chen Zheng <58443436+rchenzheng@users.noreply.github.com>
Richard Mathie <richard.mathie@amey.co.uk>
Richard Scothern <richard.scothern@gmail.com>
Rick Wieman <git@rickw.nl>
@ -591,6 +660,7 @@ Rob Gulewich <rgulewich@netflix.com>
Robert Wallis <smilingrob@gmail.com>
Robin Naundorf <r.naundorf@fh-muenster.de>
Robin Speekenbrink <robin@kingsquare.nl>
Roch Feuillade <roch.feuillade@pandobac.com>
Rodolfo Ortiz <rodolfo.ortiz@definityfirst.com>
Rogelio Canedo <rcanedo@mappy.priv>
Rohan Verma <hello@rohanverma.net>
@ -609,11 +679,13 @@ Sainath Grandhi <sainath.grandhi@intel.com>
Sakeven Jiang <jc5930@sina.cn>
Sally O'Malley <somalley@redhat.com>
Sam Neirinck <sam@samneirinck.com>
Sam Thibault <sam.thibault@docker.com>
Samarth Shah <samashah@microsoft.com>
Sambuddha Basu <sambuddhabasu1@gmail.com>
Sami Tabet <salph.tabet@gmail.com>
Samuel Cochran <sj26@sj26.com>
Samuel Karp <skarp@amazon.com>
Sandro Jäckel <sandro.jaeckel@gmail.com>
Santhosh Manohar <santhosh@docker.com>
Sargun Dhillon <sargun@netflix.com>
Saswat Bhattacharya <sas.saswat@gmail.com>
@ -643,7 +715,8 @@ Slava Semushin <semushin@redhat.com>
Solomon Hykes <solomon@docker.com>
Song Gao <song@gao.io>
Spencer Brown <spencer@spencerbrown.org>
squeegels <1674195+squeegels@users.noreply.github.com>
Spring Lee <xi.shuai@outlook.com>
squeegels <lmscrewy@gmail.com>
Srini Brahmaroutu <srbrahma@us.ibm.com>
Stefan S. <tronicum@user.github.com>
Stefan Scherer <stefan.scherer@docker.com>
@ -654,6 +727,7 @@ Stephen Rust <srust@blockbridge.com>
Steve Durrheimer <s.durrheimer@gmail.com>
Steve Richards <steve.richards@docker.com>
Steven Burgess <steven.a.burgess@hotmail.com>
Stoica-Marcu Floris-Andrei <floris.sm@gmail.com>
Subhajit Ghosh <isubuz.g@gmail.com>
Sun Jianbo <wonderflow.sun@gmail.com>
Sune Keller <absukl@almbrand.dk>
@ -665,7 +739,10 @@ Sébastien HOUZÉ <cto@verylastroom.com>
T K Sourabh <sourabhtk37@gmail.com>
TAGOMORI Satoshi <tagomoris@gmail.com>
taiji-tech <csuhqg@foxmail.com>
Takeshi Koenuma <t.koenuma2@gmail.com>
Takuya Noguchi <takninnovationresearch@gmail.com>
Taylor Jones <monitorjbl@gmail.com>
Teiva Harsanyi <t.harsanyi@thebeat.co>
Tejaswini Duggaraju <naduggar@microsoft.com>
Tengfei Wang <tfwang@alauda.io>
Teppei Fukuda <knqyf263@gmail.com>
@ -696,6 +773,7 @@ Tom Fotherby <tom+github@peopleperhour.com>
Tom Klingenberg <tklingenberg@lastflood.net>
Tom Milligan <code@tommilligan.net>
Tom X. Tobin <tomxtobin@tomxtobin.com>
Tomas Bäckman <larstomas@gmail.com>
Tomas Tomecek <ttomecek@redhat.com>
Tomasz Kopczynski <tomek@kopczynski.net.pl>
Tomáš Hrčka <thrcka@redhat.com>
@ -711,6 +789,7 @@ Ulrich Bareth <ulrich.bareth@gmail.com>
Ulysses Souza <ulysses.souza@docker.com>
Umesh Yadav <umesh4257@gmail.com>
Valentin Lorentz <progval+git@progval.net>
Vardan Pogosian <vardan.pogosyan@gmail.com>
Venkateswara Reddy Bukkasamudram <bukkasamudram@outlook.com>
Veres Lajos <vlajos@gmail.com>
Victor Vieux <victor.vieux@docker.com>
@ -757,6 +836,7 @@ Yunxiang Huang <hyxqshk@vip.qq.com>
Zachary Romero <zacromero3@gmail.com>
Zander Mackie <zmackie@gmail.com>
zebrilee <zebrilee@gmail.com>
Zeel B Patel <patel_zeel@iitgn.ac.in>
Zhang Kun <zkazure@gmail.com>
Zhang Wei <zhangwei555@huawei.com>
Zhang Wentao <zhangwentao234@huawei.com>
@ -768,4 +848,5 @@ Zhu Guihua <zhugh.fnst@cn.fujitsu.com>
Álex González <agonzalezro@gmail.com>
Álvaro Lázaro <alvaro.lazaro.g@gmail.com>
Átila Camurça Alves <camurca.home@gmail.com>
Александр Менщиков <__Singleton__@hackerdom.ru>
徐俊杰 <paco.xu@daocloud.io>

@ -19,7 +19,7 @@ const (
// ConfigFileName is the name of config file
ConfigFileName = "config.json"
configFileDir = ".docker"
oldConfigfile = ".dockercfg"
oldConfigfile = ".dockercfg" // Deprecated: remove once we stop printing deprecation warning
contextsDir = "contexts"
)
@ -84,16 +84,6 @@ func Path(p ...string) (string, error) {
return path, nil
}
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
// a non-nested reader
func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
configFile := configfile.ConfigFile{
AuthConfigs: make(map[string]types.AuthConfig),
}
err := configFile.LegacyLoadFromReader(configData)
return &configFile, err
}
// LoadFromReader is a convenience function that creates a ConfigFile object from
// a reader
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
@ -140,12 +130,8 @@ func load(configDir string) (*configfile.ConfigFile, bool, error) {
// Can't find latest config file so check for the old one
filename = filepath.Join(getHomeDir(), oldConfigfile)
if file, err := os.Open(filename); err == nil {
if _, err := os.Stat(filename); err == nil {
printLegacyFileWarning = true
defer file.Close()
if err := configFile.LegacyLoadFromReader(file); err != nil {
return configFile, printLegacyFileWarning, errors.Wrap(err, filename)
}
}
return configFile, printLegacyFileWarning, nil
}
@ -158,7 +144,7 @@ func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
fmt.Fprintf(stderr, "WARNING: Error loading config file: %v\n", err)
}
if printLegacyFileWarning {
_, _ = fmt.Fprintln(stderr, "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format is deprecated and will be removed in an upcoming release")
_, _ = fmt.Fprintln(stderr, "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format has been removed and the configuration file will be ignored")
}
if !configFile.ContainsAuth() {
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)

@ -3,9 +3,7 @@ package configfile
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -16,13 +14,6 @@ import (
"github.com/sirupsen/logrus"
)
const (
// This constant is only used for really old config files when the
// URL wasn't saved as part of the config file and it was just
// assumed to be this value.
defaultIndexServer = "https://index.docker.io/v1/"
)
// ConfigFile ~/.docker/config.json file info
type ConfigFile struct {
AuthConfigs map[string]types.AuthConfig `json:"auths"`
@ -46,8 +37,7 @@ type ConfigFile struct {
PruneFilters []string `json:"pruneFilters,omitempty"`
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
Experimental string `json:"experimental,omitempty"`
StackOrchestrator string `json:"stackOrchestrator,omitempty"`
Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"`
StackOrchestrator string `json:"stackOrchestrator,omitempty"` // Deprecated: swarm is now the default orchestrator, and this option is ignored.
CurrentContext string `json:"currentContext,omitempty"`
CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"`
Plugins map[string]map[string]string `json:"plugins,omitempty"`
@ -60,11 +50,7 @@ type ProxyConfig struct {
HTTPSProxy string `json:"httpsProxy,omitempty"`
NoProxy string `json:"noProxy,omitempty"`
FTPProxy string `json:"ftpProxy,omitempty"`
}
// KubernetesConfig contains Kubernetes orchestrator settings
type KubernetesConfig struct {
AllNamespaces string `json:"allNamespaces,omitempty"`
AllProxy string `json:"allProxy,omitempty"`
}
// New initializes an empty configuration file for the given filename 'fn'
@ -78,44 +64,6 @@ func New(fn string) *ConfigFile {
}
}
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
// auth config information with given directory and populates the receiver object
func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
b, err := ioutil.ReadAll(configData)
if err != nil {
return err
}
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
arr := strings.Split(string(b), "\n")
if len(arr) < 2 {
return errors.Errorf("The Auth config file is empty")
}
authConfig := types.AuthConfig{}
origAuth := strings.Split(arr[0], " = ")
if len(origAuth) != 2 {
return errors.Errorf("Invalid Auth config file")
}
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
if err != nil {
return err
}
authConfig.ServerAddress = defaultIndexServer
configFile.AuthConfigs[defaultIndexServer] = authConfig
} else {
for k, authConfig := range configFile.AuthConfigs {
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
if err != nil {
return err
}
authConfig.Auth = ""
authConfig.ServerAddress = k
configFile.AuthConfigs[k] = authConfig
}
}
return nil
}
// LoadFromReader reads the configuration data given and sets up the auth config
// information with given directory and populates the receiver object
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
@ -134,7 +82,7 @@ func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
ac.ServerAddress = addr
configFile.AuthConfigs[addr] = ac
}
return checkKubernetesConfiguration(configFile.Kubernetes)
return nil
}
// ContainsAuth returns whether there is authentication configured
@ -147,6 +95,9 @@ func (configFile *ConfigFile) ContainsAuth() bool {
// GetAuthConfigs returns the mapping of repo to auth configuration
func (configFile *ConfigFile) GetAuthConfigs() map[string]types.AuthConfig {
if configFile.AuthConfigs == nil {
configFile.AuthConfigs = make(map[string]types.AuthConfig)
}
return configFile.AuthConfigs
}
@ -191,10 +142,10 @@ func (configFile *ConfigFile) Save() (retErr error) {
}
dir := filepath.Dir(configFile.Filename)
if err := os.MkdirAll(dir, 0700); err != nil {
if err := os.MkdirAll(dir, 0o700); err != nil {
return err
}
temp, err := ioutil.TempFile(dir, filepath.Base(configFile.Filename))
temp, err := os.CreateTemp(dir, filepath.Base(configFile.Filename))
if err != nil {
return err
}
@ -244,6 +195,7 @@ func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts map[string]*
"HTTPS_PROXY": &config.HTTPSProxy,
"NO_PROXY": &config.NoProxy,
"FTP_PROXY": &config.FTPProxy,
"ALL_PROXY": &config.AllProxy,
}
m := runOpts
if m == nil {
@ -292,12 +244,11 @@ func decodeAuth(authStr string) (string, string, error) {
if n > decLen {
return "", "", errors.Errorf("Something went wrong decoding auth config")
}
arr := strings.SplitN(string(decoded), ":", 2)
if len(arr) != 2 {
userName, password, ok := strings.Cut(string(decoded), ":")
if !ok || userName == "" {
return "", "", errors.Errorf("Invalid auth configuration file")
}
password := strings.Trim(arr[1], "\x00")
return arr[0], password, nil
return userName, strings.Trim(password, "\x00"), nil
}
// GetCredentialsStore returns a new credentials store from the settings in the
@ -352,7 +303,8 @@ func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig,
for registryHostname := range configFile.CredentialHelpers {
newAuth, err := configFile.GetAuthConfig(registryHostname)
if err != nil {
return nil, err
logrus.WithError(err).Warnf("Failed to get credentials for registry: %s", registryHostname)
continue
}
auths[registryHostname] = newAuth
}
@ -399,17 +351,3 @@ func (configFile *ConfigFile) SetPluginConfig(pluginname, option, value string)
delete(configFile.Plugins, pluginname)
}
}
func checkKubernetesConfiguration(kubeConfig *KubernetesConfig) error {
if kubeConfig == nil {
return nil
}
switch kubeConfig.AllNamespaces {
case "":
case "enabled":
case "disabled":
default:
return fmt.Errorf("invalid 'kubernetes.allNamespaces' value, should be 'enabled' or 'disabled': %s", kubeConfig.AllNamespaces)
}
return nil
}

@ -12,7 +12,7 @@ import (
// ignoring any error during the process.
func copyFilePermissions(src, dst string) {
var (
mode os.FileMode = 0600
mode os.FileMode = 0o600
uid, gid int
)

@ -52,7 +52,8 @@ func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
// Store saves the given credentials in the file store.
func (c *fileStore) Store(authConfig types.AuthConfig) error {
c.file.GetAuthConfigs()[authConfig.ServerAddress] = authConfig
authConfigs := c.file.GetAuthConfigs()
authConfigs[authConfig.ServerAddress] = authConfig
return c.file.Save()
}
@ -75,7 +76,6 @@ func ConvertToHostname(url string) string {
stripped = strings.TrimPrefix(url, "https://")
}
nameParts := strings.SplitN(stripped, "/", 2)
return nameParts[0]
hostName, _, _ := strings.Cut(stripped, "/")
return hostName
}

@ -7,7 +7,7 @@ import (
)
const (
remoteCredentialsPrefix = "docker-credential-"
remoteCredentialsPrefix = "docker-credential-" //nolint:gosec // ignore G101: Potential hardcoded credentials
tokenUsername = "<token>"
)
@ -51,6 +51,7 @@ func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) {
auth.Username = creds.Username
auth.IdentityToken = creds.IdentityToken
auth.Password = creds.Password
auth.ServerAddress = creds.ServerAddress
return auth, nil
}
@ -76,6 +77,9 @@ func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) {
ac.Username = creds.Username
ac.Password = creds.Password
ac.IdentityToken = creds.IdentityToken
if ac.ServerAddress == "" {
ac.ServerAddress = creds.ServerAddress
}
authConfigs[registry] = ac
}

@ -4,13 +4,13 @@
// For example, to provide an http.Client that can connect to a Docker daemon
// running in a Docker container ("DIND"):
//
// httpClient := &http.Client{
// Transport: &http.Transport{
// DialContext: func(ctx context.Context, _network, _addr string) (net.Conn, error) {
// return commandconn.New(ctx, "docker", "exec", "-it", containerID, "docker", "system", "dial-stdio")
// },
// },
// }
// httpClient := &http.Client{
// Transport: &http.Transport{
// DialContext: func(ctx context.Context, _network, _addr string) (net.Conn, error) {
// return commandconn.New(ctx, "docker", "exec", "-it", containerID, "docker", "system", "dial-stdio")
// },
// },
// }
package commandconn
import (
@ -32,12 +32,12 @@ import (
)
// New returns net.Conn
func New(ctx context.Context, cmd string, args ...string) (net.Conn, error) {
func New(_ context.Context, cmd string, args ...string) (net.Conn, error) {
var (
c commandConn
err error
)
c.cmd = exec.CommandContext(ctx, cmd, args...)
c.cmd = exec.Command(cmd, args...)
// we assume that args never contains sensitive information
logrus.Debugf("commandconn: starting %s with %v", cmd, args)
c.cmd.Env = os.Environ()
@ -236,17 +236,21 @@ func (c *commandConn) Close() error {
func (c *commandConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *commandConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *commandConn) SetDeadline(t time.Time) error {
logrus.Debugf("unimplemented call: SetDeadline(%v)", t)
return nil
}
func (c *commandConn) SetReadDeadline(t time.Time) error {
logrus.Debugf("unimplemented call: SetReadDeadline(%v)", t)
return nil
}
func (c *commandConn) SetWriteDeadline(t time.Time) error {
logrus.Debugf("unimplemented call: SetWriteDeadline(%v)", t)
return nil

@ -4,10 +4,8 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"net"
"net/http"
"os"
"time"
"github.com/docker/cli/cli/connhelper"
@ -67,17 +65,10 @@ func (c *Endpoint) tlsConfig() (*tls.Config, error) {
keyBytes := c.TLSData.Key
pemBlock, _ := pem.Decode(keyBytes)
if pemBlock == nil {
return nil, fmt.Errorf("no valid private key found")
return nil, errors.New("no valid private key found")
}
var err error
// TODO should we follow Golang, and deprecate RFC 1423 encryption, and produce a warning (or just error)? see https://github.com/docker/cli/issues/3212
if x509.IsEncryptedPEMBlock(pemBlock) { //nolint: staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design
keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(c.TLSPassword)) //nolint: staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design
if err != nil {
return nil, errors.Wrap(err, "private key is encrypted, but could not decrypt it")
}
keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes})
if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design
return nil, errors.New("private key is encrypted - support for encrypted private keys has been removed, see https://docs.docker.com/go/deprecated/")
}
x509cert, err := tls.X509KeyPair(c.TLSData.Cert, keyBytes)
@ -130,12 +121,7 @@ func (c *Endpoint) ClientOpts() ([]client.Opt, error) {
}
}
version := os.Getenv("DOCKER_API_VERSION")
if version != "" {
result = append(result, client.WithVersion(version))
} else {
result = append(result, client.WithAPIVersionNegotiation())
}
result = append(result, client.WithVersionFromEnv(), client.WithAPIVersionNegotiation())
return result, nil
}

@ -1,22 +1,32 @@
// Package store provides a generic way to store credentials to connect to virtually any kind of remote system.
// The term `context` comes from the similar feature in Kubernetes kubectl config files.
// Package store provides a generic way to store credentials to connect to
// virtually any kind of remote system.
// The term `context` comes from the similar feature in Kubernetes kubectl
// config files.
//
// Conceptually, a context is a set of metadata and TLS data, that can be used to connect to various endpoints
// of a remote system. TLS data and metadata are stored separately, so that in the future, we will be able to store sensitive
// information in a more secure way, depending on the os we are running on (e.g.: on Windows we could use the user Certificate Store, on Mac OS the user Keychain...).
// Conceptually, a context is a set of metadata and TLS data, that can be used
// to connect to various endpoints of a remote system. TLS data and metadata
// are stored separately, so that in the future, we will be able to store
// sensitive information in a more secure way, depending on the os we are running
// on (e.g.: on Windows we could use the user Certificate Store, on macOS the
// user Keychain...).
//
// Current implementation is purely file based with the following structure:
// ${CONTEXT_ROOT}
// - meta/
// - <context id>/meta.json: contains context medata (key/value pairs) as well as a list of endpoints (themselves containing key/value pair metadata)
// - tls/
// - <context id>/endpoint1/: directory containing TLS data for the endpoint1 in the corresponding context
//
// The context store itself has absolutely no knowledge about what a docker or a kubernetes endpoint should contain in term of metadata or TLS config.
// Client code is responsible for generating and parsing endpoint metadata and TLS files.
// The multi-endpoints approach of this package allows to combine many different endpoints in the same "context" (e.g., the Docker CLI
// is able for a single context to define both a docker endpoint and a Kubernetes endpoint for the same cluster, and also specify which
// orchestrator to use by default when deploying a compose stack on this cluster).
// ${CONTEXT_ROOT}
// meta/
// <context id>/meta.json: contains context medata (key/value pairs) as
// well as a list of endpoints (themselves containing
// key/value pair metadata).
// tls/
// <context id>/endpoint1/: directory containing TLS data for the endpoint1
// in the corresponding context.
//
// Context IDs are actually SHA256 hashes of the context name, and are there only to avoid dealing with special characters in context names.
// The context store itself has absolutely no knowledge about what a docker
// endpoint should contain in term of metadata or TLS config. Client code is
// responsible for generating and parsing endpoint metadata and TLS files. The
// multi-endpoints approach of this package allows to combine many different
// endpoints in the same "context".
//
// Context IDs are actually SHA256 hashes of the context name, and are there
// only to avoid dealing with special characters in context names.
package store

@ -3,13 +3,15 @@ package store
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/ioutils"
"github.com/fvbommel/sortorder"
"github.com/pkg/errors"
)
const (
@ -28,14 +30,14 @@ func (s *metadataStore) contextDir(id contextdir) string {
func (s *metadataStore) createOrUpdate(meta Metadata) error {
contextDir := s.contextDir(contextdirOf(meta.Name))
if err := os.MkdirAll(contextDir, 0755); err != nil {
if err := os.MkdirAll(contextDir, 0o755); err != nil {
return err
}
bytes, err := json.Marshal(&meta)
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(contextDir, metaFile), bytes, 0644)
return ioutils.AtomicWriteFile(filepath.Join(contextDir, metaFile), bytes, 0o644)
}
func parseTypedOrMap(payload []byte, getter TypeGetter) (interface{}, error) {
@ -56,49 +58,65 @@ func parseTypedOrMap(payload []byte, getter TypeGetter) (interface{}, error) {
return reflect.ValueOf(typed).Elem().Interface(), nil
}
func (s *metadataStore) get(id contextdir) (Metadata, error) {
contextDir := s.contextDir(id)
bytes, err := ioutil.ReadFile(filepath.Join(contextDir, metaFile))
func (s *metadataStore) get(name string) (Metadata, error) {
m, err := s.getByID(contextdirOf(name))
if err != nil {
return Metadata{}, convertContextDoesNotExist(err)
return m, errors.Wrapf(err, "context %q", name)
}
return m, nil
}
func (s *metadataStore) getByID(id contextdir) (Metadata, error) {
fileName := filepath.Join(s.contextDir(id), metaFile)
bytes, err := os.ReadFile(fileName)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return Metadata{}, errdefs.NotFound(errors.Wrap(err, "context not found"))
}
return Metadata{}, err
}
var untyped untypedContextMetadata
r := Metadata{
Endpoints: make(map[string]interface{}),
}
if err := json.Unmarshal(bytes, &untyped); err != nil {
return Metadata{}, err
return Metadata{}, fmt.Errorf("parsing %s: %v", fileName, err)
}
r.Name = untyped.Name
if r.Metadata, err = parseTypedOrMap(untyped.Metadata, s.config.contextType); err != nil {
return Metadata{}, err
return Metadata{}, fmt.Errorf("parsing %s: %v", fileName, err)
}
for k, v := range untyped.Endpoints {
if r.Endpoints[k], err = parseTypedOrMap(v, s.config.endpointTypes[k]); err != nil {
return Metadata{}, err
return Metadata{}, fmt.Errorf("parsing %s: %v", fileName, err)
}
}
return r, err
}
func (s *metadataStore) remove(id contextdir) error {
contextDir := s.contextDir(id)
return os.RemoveAll(contextDir)
func (s *metadataStore) remove(name string) error {
if err := os.RemoveAll(s.contextDir(contextdirOf(name))); err != nil {
return errors.Wrapf(err, "failed to remove metadata")
}
return nil
}
func (s *metadataStore) list() ([]Metadata, error) {
ctxDirs, err := listRecursivelyMetadataDirs(s.root)
if err != nil {
if os.IsNotExist(err) {
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
return nil, err
}
var res []Metadata
for _, dir := range ctxDirs {
c, err := s.get(contextdir(dir))
c, err := s.getByID(contextdir(dir))
if err != nil {
return nil, err
if errors.Is(err, os.ErrNotExist) {
continue
}
return nil, errors.Wrap(err, "failed to read metadata")
}
res = append(res, c)
}
@ -117,7 +135,7 @@ func isContextDir(path string) bool {
}
func listRecursivelyMetadataDirs(root string) ([]string, error) {
fis, err := ioutil.ReadDir(root)
fis, err := os.ReadDir(root)
if err != nil {
return nil, err
}
@ -132,20 +150,13 @@ func listRecursivelyMetadataDirs(root string) ([]string, error) {
return nil, err
}
for _, s := range subs {
result = append(result, fmt.Sprintf("%s/%s", fi.Name(), s))
result = append(result, filepath.Join(fi.Name(), s))
}
}
}
return result, nil
}
func convertContextDoesNotExist(err error) error {
if os.IsNotExist(err) {
return &contextDoesNotExistError{}
}
return err
}
type untypedContextMetadata struct {
Metadata json.RawMessage `json:"metadata,omitempty"`
Endpoints map[string]json.RawMessage `json:"endpoints,omitempty"`

@ -7,9 +7,7 @@ import (
"bytes"
_ "crypto/sha256" // ensure ids can be computed
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"path"
"path/filepath"
@ -17,7 +15,7 @@ import (
"strings"
"github.com/docker/docker/errdefs"
digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
@ -95,11 +93,11 @@ type ContextTLSData struct {
// New creates a store from a given directory.
// If the directory does not exist or is empty, initialize it
func New(dir string, cfg Config) Store {
func New(dir string, cfg Config) *ContextStore {
metaRoot := filepath.Join(dir, metadataDir)
tlsRoot := filepath.Join(dir, tlsDir)
return &store{
return &ContextStore{
meta: &metadataStore{
root: metaRoot,
config: cfg,
@ -110,82 +108,106 @@ func New(dir string, cfg Config) Store {
}
}
type store struct {
// ContextStore implements Store.
type ContextStore struct {
meta *metadataStore
tls *tlsStore
}
func (s *store) List() ([]Metadata, error) {
// List return all contexts.
func (s *ContextStore) List() ([]Metadata, error) {
return s.meta.list()
}
func (s *store) CreateOrUpdate(meta Metadata) error {
// Names return Metadata names for a Lister
func Names(s Lister) ([]string, error) {
list, err := s.List()
if err != nil {
return nil, err
}
var names []string
for _, item := range list {
names = append(names, item.Name)
}
return names, nil
}
// CreateOrUpdate creates or updates metadata for the context.
func (s *ContextStore) CreateOrUpdate(meta Metadata) error {
return s.meta.createOrUpdate(meta)
}
func (s *store) Remove(name string) error {
id := contextdirOf(name)
if err := s.meta.remove(id); err != nil {
return patchErrContextName(err, name)
// Remove deletes the context with the given name, if found.
func (s *ContextStore) Remove(name string) error {
if err := s.meta.remove(name); err != nil {
return errors.Wrapf(err, "failed to remove context %s", name)
}
return patchErrContextName(s.tls.removeAllContextData(id), name)
if err := s.tls.remove(name); err != nil {
return errors.Wrapf(err, "failed to remove context %s", name)
}
return nil
}
func (s *store) GetMetadata(name string) (Metadata, error) {
res, err := s.meta.get(contextdirOf(name))
patchErrContextName(err, name)
return res, err
// GetMetadata returns the metadata for the context with the given name.
// It returns an errdefs.ErrNotFound if the context was not found.
func (s *ContextStore) GetMetadata(name string) (Metadata, error) {
return s.meta.get(name)
}
func (s *store) ResetTLSMaterial(name string, data *ContextTLSData) error {
id := contextdirOf(name)
if err := s.tls.removeAllContextData(id); err != nil {
return patchErrContextName(err, name)
// ResetTLSMaterial removes TLS data for all endpoints in the context and replaces
// it with the new data.
func (s *ContextStore) ResetTLSMaterial(name string, data *ContextTLSData) error {
if err := s.tls.remove(name); err != nil {
return err
}
if data == nil {
return nil
}
for ep, files := range data.Endpoints {
for fileName, data := range files.Files {
if err := s.tls.createOrUpdate(id, ep, fileName, data); err != nil {
return patchErrContextName(err, name)
if err := s.tls.createOrUpdate(name, ep, fileName, data); err != nil {
return err
}
}
}
return nil
}
func (s *store) ResetEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error {
id := contextdirOf(contextName)
if err := s.tls.removeAllEndpointData(id, endpointName); err != nil {
return patchErrContextName(err, contextName)
// ResetEndpointTLSMaterial removes TLS data for the given context and endpoint,
// and replaces it with the new data.
func (s *ContextStore) ResetEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error {
if err := s.tls.removeEndpoint(contextName, endpointName); err != nil {
return err
}
if data == nil {
return nil
}
for fileName, data := range data.Files {
if err := s.tls.createOrUpdate(id, endpointName, fileName, data); err != nil {
return patchErrContextName(err, contextName)
if err := s.tls.createOrUpdate(contextName, endpointName, fileName, data); err != nil {
return err
}
}
return nil
}
func (s *store) ListTLSFiles(name string) (map[string]EndpointFiles, error) {
res, err := s.tls.listContextData(contextdirOf(name))
return res, patchErrContextName(err, name)
// ListTLSFiles returns the list of TLS files present for each endpoint in the
// context.
func (s *ContextStore) ListTLSFiles(name string) (map[string]EndpointFiles, error) {
return s.tls.listContextData(name)
}
func (s *store) GetTLSData(contextName, endpointName, fileName string) ([]byte, error) {
res, err := s.tls.getData(contextdirOf(contextName), endpointName, fileName)
return res, patchErrContextName(err, contextName)
// GetTLSData reads, and returns the content of the given fileName for an endpoint.
// It returns an errdefs.ErrNotFound if the file was not found.
func (s *ContextStore) GetTLSData(contextName, endpointName, fileName string) ([]byte, error) {
return s.tls.getData(contextName, endpointName, fileName)
}
func (s *store) GetStorageInfo(contextName string) StorageInfo {
dir := contextdirOf(contextName)
// GetStorageInfo returns the paths where the Metadata and TLS data are stored
// for the context.
func (s *ContextStore) GetStorageInfo(contextName string) StorageInfo {
return StorageInfo{
MetadataPath: s.meta.contextDir(dir),
TLSPath: s.tls.contextDir(dir),
MetadataPath: s.meta.contextDir(contextdirOf(contextName)),
TLSPath: s.tls.contextDir(contextName),
}
}
@ -198,7 +220,7 @@ func ValidateContextName(name string) error {
return errors.New(`"default" is a reserved context name`)
}
if !restrictedNameRegEx.MatchString(name) {
return fmt.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern)
return errors.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern)
}
return nil
}
@ -224,7 +246,7 @@ func Export(name string, s Reader) io.ReadCloser {
}
if err = tw.WriteHeader(&tar.Header{
Name: metaFile,
Mode: 0644,
Mode: 0o644,
Size: int64(len(metaBytes)),
}); err != nil {
writer.CloseWithError(err)
@ -241,7 +263,7 @@ func Export(name string, s Reader) io.ReadCloser {
}
if err = tw.WriteHeader(&tar.Header{
Name: "tls",
Mode: 0700,
Mode: 0o700,
Size: 0,
Typeflag: tar.TypeDir,
}); err != nil {
@ -251,7 +273,7 @@ func Export(name string, s Reader) io.ReadCloser {
for endpointName, endpointFiles := range tlsFiles {
if err = tw.WriteHeader(&tar.Header{
Name: path.Join("tls", endpointName),
Mode: 0700,
Mode: 0o700,
Size: 0,
Typeflag: tar.TypeDir,
}); err != nil {
@ -266,7 +288,7 @@ func Export(name string, s Reader) io.ReadCloser {
}
if err = tw.WriteHeader(&tar.Header{
Name: path.Join("tls", endpointName, fileName),
Mode: 0600,
Mode: 0o600,
Size: int64(len(data)),
}); err != nil {
writer.CloseWithError(err)
@ -349,7 +371,7 @@ func importTar(name string, s Writer, reader io.Reader) error {
return errors.Wrap(err, hdr.Name)
}
if hdr.Name == metaFile {
data, err := ioutil.ReadAll(tr)
data, err := io.ReadAll(tr)
if err != nil {
return err
}
@ -362,7 +384,7 @@ func importTar(name string, s Writer, reader io.Reader) error {
}
importedMetaFile = true
} else if strings.HasPrefix(hdr.Name, "tls/") {
data, err := ioutil.ReadAll(tr)
data, err := io.ReadAll(tr)
if err != nil {
return err
}
@ -378,7 +400,7 @@ func importTar(name string, s Writer, reader io.Reader) error {
}
func importZip(name string, s Writer, reader io.Reader) error {
body, err := ioutil.ReadAll(&LimitedReader{R: reader, N: maxAllowedFileSizeToImport})
body, err := io.ReadAll(&LimitedReader{R: reader, N: maxAllowedFileSizeToImport})
if err != nil {
return err
}
@ -406,7 +428,7 @@ func importZip(name string, s Writer, reader io.Reader) error {
return err
}
data, err := ioutil.ReadAll(&LimitedReader{R: f, N: maxAllowedFileSizeToImport})
data, err := io.ReadAll(&LimitedReader{R: f, N: maxAllowedFileSizeToImport})
defer f.Close()
if err != nil {
return err
@ -424,7 +446,7 @@ func importZip(name string, s Writer, reader io.Reader) error {
if err != nil {
return err
}
data, err := ioutil.ReadAll(f)
data, err := io.ReadAll(f)
defer f.Close()
if err != nil {
return err
@ -472,58 +494,18 @@ func importEndpointTLS(tlsData *ContextTLSData, path string, data []byte) error
return nil
}
type setContextName interface {
setContext(name string)
}
type contextDoesNotExistError struct {
name string
}
func (e *contextDoesNotExistError) Error() string {
return fmt.Sprintf("context %q does not exist", e.name)
}
func (e *contextDoesNotExistError) setContext(name string) {
e.name = name
}
// NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound
func (e *contextDoesNotExistError) NotFound() {}
type tlsDataDoesNotExist interface {
errdefs.ErrNotFound
IsTLSDataDoesNotExist()
}
type tlsDataDoesNotExistError struct {
context, endpoint, file string
}
func (e *tlsDataDoesNotExistError) Error() string {
return fmt.Sprintf("tls data for %s/%s/%s does not exist", e.context, e.endpoint, e.file)
}
func (e *tlsDataDoesNotExistError) setContext(name string) {
e.context = name
}
// NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound
func (e *tlsDataDoesNotExistError) NotFound() {}
// IsTLSDataDoesNotExist satisfies tlsDataDoesNotExist
func (e *tlsDataDoesNotExistError) IsTLSDataDoesNotExist() {}
// IsErrContextDoesNotExist checks if the given error is a "context does not exist" condition
// IsErrContextDoesNotExist checks if the given error is a "context does not exist" condition.
//
// Deprecated: use github.com/docker/docker/errdefs.IsNotFound()
func IsErrContextDoesNotExist(err error) bool {
_, ok := err.(*contextDoesNotExistError)
return ok
return errdefs.IsNotFound(err)
}
// IsErrTLSDataDoesNotExist checks if the given error is a "context does not exist" condition
//
// Deprecated: use github.com/docker/docker/errdefs.IsNotFound()
func IsErrTLSDataDoesNotExist(err error) bool {
_, ok := err.(tlsDataDoesNotExist)
return ok
return errdefs.IsNotFound(err)
}
type contextdir string
@ -531,10 +513,3 @@ type contextdir string
func contextdirOf(name string) contextdir {
return contextdir(digest.FromString(name).Encoded())
}
func patchErrContextName(err error, name string) error {
if typed, ok := err.(setContextName); ok {
typed.setContext(name)
}
return err
}

@ -19,7 +19,7 @@ func EndpointTypeGetter(name string, getter TypeGetter) NamedTypeGetter {
}
}
// Config is used to configure the metadata marshaler of the context store
// Config is used to configure the metadata marshaler of the context ContextStore
type Config struct {
contextType TypeGetter
endpointTypes map[string]TypeGetter

@ -1,9 +1,12 @@
package store
import (
"io/ioutil"
"os"
"path/filepath"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/ioutils"
"github.com/pkg/errors"
)
const tlsDir = "tls"
@ -12,69 +15,70 @@ type tlsStore struct {
root string
}
func (s *tlsStore) contextDir(id contextdir) string {
return filepath.Join(s.root, string(id))
}
func (s *tlsStore) endpointDir(contextID contextdir, name string) string {
return filepath.Join(s.root, string(contextID), name)
func (s *tlsStore) contextDir(name string) string {
return filepath.Join(s.root, string(contextdirOf(name)))
}
func (s *tlsStore) filePath(contextID contextdir, endpointName, filename string) string {
return filepath.Join(s.root, string(contextID), endpointName, filename)
func (s *tlsStore) endpointDir(name, endpointName string) string {
return filepath.Join(s.contextDir(name), endpointName)
}
func (s *tlsStore) createOrUpdate(contextID contextdir, endpointName, filename string, data []byte) error {
epdir := s.endpointDir(contextID, endpointName)
func (s *tlsStore) createOrUpdate(name, endpointName, filename string, data []byte) error {
parentOfRoot := filepath.Dir(s.root)
if err := os.MkdirAll(parentOfRoot, 0755); err != nil {
if err := os.MkdirAll(parentOfRoot, 0o755); err != nil {
return err
}
if err := os.MkdirAll(epdir, 0700); err != nil {
endpointDir := s.endpointDir(name, endpointName)
if err := os.MkdirAll(endpointDir, 0o700); err != nil {
return err
}
return ioutil.WriteFile(s.filePath(contextID, endpointName, filename), data, 0600)
return ioutils.AtomicWriteFile(filepath.Join(endpointDir, filename), data, 0o600)
}
func (s *tlsStore) getData(contextID contextdir, endpointName, filename string) ([]byte, error) {
data, err := ioutil.ReadFile(s.filePath(contextID, endpointName, filename))
func (s *tlsStore) getData(name, endpointName, filename string) ([]byte, error) {
data, err := os.ReadFile(filepath.Join(s.endpointDir(name, endpointName), filename))
if err != nil {
return nil, convertTLSDataDoesNotExist(endpointName, filename, err)
if os.IsNotExist(err) {
return nil, errdefs.NotFound(errors.Errorf("TLS data for %s/%s/%s does not exist", name, endpointName, filename))
}
return nil, errors.Wrapf(err, "failed to read TLS data for endpoint %s", endpointName)
}
return data, nil
}
func (s *tlsStore) remove(contextID contextdir, endpointName, filename string) error {
err := os.Remove(s.filePath(contextID, endpointName, filename))
if os.IsNotExist(err) {
return nil
// remove deletes all TLS data for the given context.
func (s *tlsStore) remove(name string) error {
if err := os.RemoveAll(s.contextDir(name)); err != nil {
return errors.Wrapf(err, "failed to remove TLS data")
}
return err
}
func (s *tlsStore) removeAllEndpointData(contextID contextdir, endpointName string) error {
return os.RemoveAll(s.endpointDir(contextID, endpointName))
return nil
}
func (s *tlsStore) removeAllContextData(contextID contextdir) error {
return os.RemoveAll(s.contextDir(contextID))
func (s *tlsStore) removeEndpoint(name, endpointName string) error {
if err := os.RemoveAll(s.endpointDir(name, endpointName)); err != nil {
return errors.Wrapf(err, "failed to remove TLS data for endpoint %s", endpointName)
}
return nil
}
func (s *tlsStore) listContextData(contextID contextdir) (map[string]EndpointFiles, error) {
epFSs, err := ioutil.ReadDir(s.contextDir(contextID))
func (s *tlsStore) listContextData(name string) (map[string]EndpointFiles, error) {
contextDir := s.contextDir(name)
epFSs, err := os.ReadDir(contextDir)
if err != nil {
if os.IsNotExist(err) {
return map[string]EndpointFiles{}, nil
}
return nil, err
return nil, errors.Wrapf(err, "failed to list TLS files for context %s", name)
}
r := make(map[string]EndpointFiles)
for _, epFS := range epFSs {
if epFS.IsDir() {
epDir := s.endpointDir(contextID, epFS.Name())
fss, err := ioutil.ReadDir(epDir)
fss, err := os.ReadDir(filepath.Join(contextDir, epFS.Name()))
if os.IsNotExist(err) {
continue
}
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to list TLS files for endpoint %s", epFS.Name())
}
var files EndpointFiles
for _, fs := range fss {
@ -90,10 +94,3 @@ func (s *tlsStore) listContextData(contextID contextdir) (map[string]EndpointFil
// EndpointFiles is a slice of strings representing file names
type EndpointFiles []string
func convertTLSDataDoesNotExist(endpoint, file string, err error) error {
if os.IsNotExist(err) {
return &tlsDataDoesNotExistError{endpoint: endpoint, file: file}
}
return err
}

@ -1,7 +1,7 @@
package context
import (
"io/ioutil"
"os"
"github.com/docker/cli/cli/context/store"
"github.com/pkg/errors"
@ -45,14 +45,14 @@ func (data *TLSData) ToStoreTLSData() *store.EndpointTLSData {
func LoadTLSData(s store.Reader, contextName, endpointName string) (*TLSData, error) {
tlsFiles, err := s.ListTLSFiles(contextName)
if err != nil {
return nil, errors.Wrapf(err, "failed to retrieve context tls files for context %q", contextName)
return nil, errors.Wrapf(err, "failed to retrieve TLS files for context %q", contextName)
}
if epTLSFiles, ok := tlsFiles[endpointName]; ok {
var tlsData TLSData
for _, f := range epTLSFiles {
data, err := s.GetTLSData(contextName, endpointName, f)
if err != nil {
return nil, errors.Wrapf(err, "failed to retrieve context tls data for file %q of context %q", f, contextName)
return nil, errors.Wrapf(err, "failed to retrieve TLS data (%s) for context %q", f, contextName)
}
switch f {
case caKey:
@ -62,7 +62,7 @@ func LoadTLSData(s store.Reader, contextName, endpointName string) (*TLSData, er
case keyKey:
tlsData.Key = data
default:
logrus.Warnf("unknown file %s in context %s tls bundle", f, contextName)
logrus.Warnf("unknown file in context %s TLS bundle: %s", contextName, f)
}
}
return &tlsData, nil
@ -77,17 +77,17 @@ func TLSDataFromFiles(caPath, certPath, keyPath string) (*TLSData, error) {
err error
)
if caPath != "" {
if ca, err = ioutil.ReadFile(caPath); err != nil {
if ca, err = os.ReadFile(caPath); err != nil {
return nil, err
}
}
if certPath != "" {
if cert, err = ioutil.ReadFile(certPath); err != nil {
if cert, err = os.ReadFile(certPath); err != nil {
return nil, err
}
}
if keyPath != "" {
if key, err = ioutil.ReadFile(keyPath); err != nil {
if key, err = os.ReadFile(keyPath); err != nil {
return nil, err
}
}

@ -0,0 +1,51 @@
package ioutils // import "github.com/docker/docker/pkg/ioutils"
import (
"errors"
"io"
)
var errBufferFull = errors.New("buffer is full")
type fixedBuffer struct {
buf []byte
pos int
lastRead int
}
func (b *fixedBuffer) Write(p []byte) (int, error) {
n := copy(b.buf[b.pos:cap(b.buf)], p)
b.pos += n
if n < len(p) {
if b.pos == cap(b.buf) {
return n, errBufferFull
}
return n, io.ErrShortWrite
}
return n, nil
}
func (b *fixedBuffer) Read(p []byte) (int, error) {
n := copy(p, b.buf[b.lastRead:b.pos])
b.lastRead += n
return n, nil
}
func (b *fixedBuffer) Len() int {
return b.pos - b.lastRead
}
func (b *fixedBuffer) Cap() int {
return cap(b.buf)
}
func (b *fixedBuffer) Reset() {
b.pos = 0
b.lastRead = 0
b.buf = b.buf[:0]
}
func (b *fixedBuffer) String() string {
return string(b.buf[b.lastRead:b.pos])
}

@ -0,0 +1,187 @@
package ioutils // import "github.com/docker/docker/pkg/ioutils"
import (
"errors"
"io"
"sync"
)
// maxCap is the highest capacity to use in byte slices that buffer data.
const maxCap = 1e6
// minCap is the lowest capacity to use in byte slices that buffer data
const minCap = 64
// blockThreshold is the minimum number of bytes in the buffer which will cause
// a write to BytesPipe to block when allocating a new slice.
const blockThreshold = 1e6
var (
// ErrClosed is returned when Write is called on a closed BytesPipe.
ErrClosed = errors.New("write to closed BytesPipe")
bufPools = make(map[int]*sync.Pool)
bufPoolsLock sync.Mutex
)
// BytesPipe is io.ReadWriteCloser which works similarly to pipe(queue).
// All written data may be read at most once. Also, BytesPipe allocates
// and releases new byte slices to adjust to current needs, so the buffer
// won't be overgrown after peak loads.
type BytesPipe struct {
mu sync.Mutex
wait *sync.Cond
buf []*fixedBuffer
bufLen int
closeErr error // error to return from next Read. set to nil if not closed.
readBlock bool // check read BytesPipe is Wait() or not
}
// NewBytesPipe creates new BytesPipe, initialized by specified slice.
// If buf is nil, then it will be initialized with slice which cap is 64.
// buf will be adjusted in a way that len(buf) == 0, cap(buf) == cap(buf).
func NewBytesPipe() *BytesPipe {
bp := &BytesPipe{}
bp.buf = append(bp.buf, getBuffer(minCap))
bp.wait = sync.NewCond(&bp.mu)
return bp
}
// Write writes p to BytesPipe.
// It can allocate new []byte slices in a process of writing.
func (bp *BytesPipe) Write(p []byte) (int, error) {
bp.mu.Lock()
defer bp.mu.Unlock()
written := 0
loop0:
for {
if bp.closeErr != nil {
return written, ErrClosed
}
if len(bp.buf) == 0 {
bp.buf = append(bp.buf, getBuffer(64))
}
// get the last buffer
b := bp.buf[len(bp.buf)-1]
n, err := b.Write(p)
written += n
bp.bufLen += n
// errBufferFull is an error we expect to get if the buffer is full
if err != nil && err != errBufferFull {
bp.wait.Broadcast()
return written, err
}
// if there was enough room to write all then break
if len(p) == n {
break
}
// more data: write to the next slice
p = p[n:]
// make sure the buffer doesn't grow too big from this write
for bp.bufLen >= blockThreshold {
if bp.readBlock {
bp.wait.Broadcast()
}
bp.wait.Wait()
if bp.closeErr != nil {
continue loop0
}
}
// add new byte slice to the buffers slice and continue writing
nextCap := b.Cap() * 2
if nextCap > maxCap {
nextCap = maxCap
}
bp.buf = append(bp.buf, getBuffer(nextCap))
}
bp.wait.Broadcast()
return written, nil
}
// CloseWithError causes further reads from a BytesPipe to return immediately.
func (bp *BytesPipe) CloseWithError(err error) error {
bp.mu.Lock()
if err != nil {
bp.closeErr = err
} else {
bp.closeErr = io.EOF
}
bp.wait.Broadcast()
bp.mu.Unlock()
return nil
}
// Close causes further reads from a BytesPipe to return immediately.
func (bp *BytesPipe) Close() error {
return bp.CloseWithError(nil)
}
// Read reads bytes from BytesPipe.
// Data could be read only once.
func (bp *BytesPipe) Read(p []byte) (n int, err error) {
bp.mu.Lock()
defer bp.mu.Unlock()
if bp.bufLen == 0 {
if bp.closeErr != nil {
return 0, bp.closeErr
}
bp.readBlock = true
bp.wait.Wait()
bp.readBlock = false
if bp.bufLen == 0 && bp.closeErr != nil {
return 0, bp.closeErr
}
}
for bp.bufLen > 0 {
b := bp.buf[0]
read, _ := b.Read(p) // ignore error since fixedBuffer doesn't really return an error
n += read
bp.bufLen -= read
if b.Len() == 0 {
// it's empty so return it to the pool and move to the next one
returnBuffer(b)
bp.buf[0] = nil
bp.buf = bp.buf[1:]
}
if len(p) == read {
break
}
p = p[read:]
}
bp.wait.Broadcast()
return
}
func returnBuffer(b *fixedBuffer) {
b.Reset()
bufPoolsLock.Lock()
pool := bufPools[b.Cap()]
bufPoolsLock.Unlock()
if pool != nil {
pool.Put(b)
}
}
func getBuffer(size int) *fixedBuffer {
bufPoolsLock.Lock()
pool, ok := bufPools[size]
if !ok {
pool = &sync.Pool{New: func() interface{} { return &fixedBuffer{buf: make([]byte, 0, size)} }}
bufPools[size] = pool
}
bufPoolsLock.Unlock()
return pool.Get().(*fixedBuffer)
}

@ -0,0 +1,161 @@
package ioutils // import "github.com/docker/docker/pkg/ioutils"
import (
"io"
"os"
"path/filepath"
)
// NewAtomicFileWriter returns WriteCloser so that writing to it writes to a
// temporary file and closing it atomically changes the temporary file to
// destination path. Writing and closing concurrently is not allowed.
func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, error) {
f, err := os.CreateTemp(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
if err != nil {
return nil, err
}
abspath, err := filepath.Abs(filename)
if err != nil {
return nil, err
}
return &atomicFileWriter{
f: f,
fn: abspath,
perm: perm,
}, nil
}
// AtomicWriteFile atomically writes data to a file named by filename.
func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
f, err := NewAtomicFileWriter(filename, perm)
if err != nil {
return err
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
f.(*atomicFileWriter).writeErr = err
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}
type atomicFileWriter struct {
f *os.File
fn string
writeErr error
perm os.FileMode
}
func (w *atomicFileWriter) Write(dt []byte) (int, error) {
n, err := w.f.Write(dt)
if err != nil {
w.writeErr = err
}
return n, err
}
func (w *atomicFileWriter) Close() (retErr error) {
defer func() {
if retErr != nil || w.writeErr != nil {
os.Remove(w.f.Name())
}
}()
if err := w.f.Sync(); err != nil {
w.f.Close()
return err
}
if err := w.f.Close(); err != nil {
return err
}
if err := os.Chmod(w.f.Name(), w.perm); err != nil {
return err
}
if w.writeErr == nil {
return os.Rename(w.f.Name(), w.fn)
}
return nil
}
// AtomicWriteSet is used to atomically write a set
// of files and ensure they are visible at the same time.
// Must be committed to a new directory.
type AtomicWriteSet struct {
root string
}
// NewAtomicWriteSet creates a new atomic write set to
// atomically create a set of files. The given directory
// is used as the base directory for storing files before
// commit. If no temporary directory is given the system
// default is used.
func NewAtomicWriteSet(tmpDir string) (*AtomicWriteSet, error) {
td, err := os.MkdirTemp(tmpDir, "write-set-")
if err != nil {
return nil, err
}
return &AtomicWriteSet{
root: td,
}, nil
}
// WriteFile writes a file to the set, guaranteeing the file
// has been synced.
func (ws *AtomicWriteSet) WriteFile(filename string, data []byte, perm os.FileMode) error {
f, err := ws.FileWriter(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}
type syncFileCloser struct {
*os.File
}
func (w syncFileCloser) Close() error {
err := w.File.Sync()
if err1 := w.File.Close(); err == nil {
err = err1
}
return err
}
// FileWriter opens a file writer inside the set. The file
// should be synced and closed before calling commit.
func (ws *AtomicWriteSet) FileWriter(name string, flag int, perm os.FileMode) (io.WriteCloser, error) {
f, err := os.OpenFile(filepath.Join(ws.root, name), flag, perm)
if err != nil {
return nil, err
}
return syncFileCloser{f}, nil
}
// Cancel cancels the set and removes all temporary data
// created in the set.
func (ws *AtomicWriteSet) Cancel() error {
return os.RemoveAll(ws.root)
}
// Commit moves all created files to the target directory. The
// target directory must not exist and the parent of the target
// directory must exist.
func (ws *AtomicWriteSet) Commit(target string) error {
return os.Rename(ws.root, target)
}
// String returns the location the set is writing to.
func (ws *AtomicWriteSet) String() string {
return ws.root
}

@ -0,0 +1,172 @@
package ioutils // import "github.com/docker/docker/pkg/ioutils"
import (
"context"
"io"
"runtime/debug"
"sync/atomic"
// make sure crypto.SHA256, crypto.sha512 and crypto.SHA384 are registered
// TODO remove once https://github.com/opencontainers/go-digest/pull/64 is merged.
_ "crypto/sha256"
_ "crypto/sha512"
"github.com/sirupsen/logrus"
)
// ReadCloserWrapper wraps an io.Reader, and implements an io.ReadCloser
// It calls the given callback function when closed. It should be constructed
// with NewReadCloserWrapper
type ReadCloserWrapper struct {
io.Reader
closer func() error
closed atomic.Bool
}
// Close calls back the passed closer function
func (r *ReadCloserWrapper) Close() error {
if !r.closed.CompareAndSwap(false, true) {
subsequentCloseWarn("ReadCloserWrapper")
return nil
}
return r.closer()
}
// NewReadCloserWrapper returns a new io.ReadCloser.
func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser {
return &ReadCloserWrapper{
Reader: r,
closer: closer,
}
}
type readerErrWrapper struct {
reader io.Reader
closer func()
}
func (r *readerErrWrapper) Read(p []byte) (int, error) {
n, err := r.reader.Read(p)
if err != nil {
r.closer()
}
return n, err
}
// NewReaderErrWrapper returns a new io.Reader.
func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader {
return &readerErrWrapper{
reader: r,
closer: closer,
}
}
// OnEOFReader wraps an io.ReadCloser and a function
// the function will run at the end of file or close the file.
type OnEOFReader struct {
Rc io.ReadCloser
Fn func()
}
func (r *OnEOFReader) Read(p []byte) (n int, err error) {
n, err = r.Rc.Read(p)
if err == io.EOF {
r.runFunc()
}
return
}
// Close closes the file and run the function.
func (r *OnEOFReader) Close() error {
err := r.Rc.Close()
r.runFunc()
return err
}
func (r *OnEOFReader) runFunc() {
if fn := r.Fn; fn != nil {
fn()
r.Fn = nil
}
}
// cancelReadCloser wraps an io.ReadCloser with a context for cancelling read
// operations.
type cancelReadCloser struct {
cancel func()
pR *io.PipeReader // Stream to read from
pW *io.PipeWriter
closed atomic.Bool
}
// NewCancelReadCloser creates a wrapper that closes the ReadCloser when the
// context is cancelled. The returned io.ReadCloser must be closed when it is
// no longer needed.
func NewCancelReadCloser(ctx context.Context, in io.ReadCloser) io.ReadCloser {
pR, pW := io.Pipe()
// Create a context used to signal when the pipe is closed
doneCtx, cancel := context.WithCancel(context.Background())
p := &cancelReadCloser{
cancel: cancel,
pR: pR,
pW: pW,
}
go func() {
_, err := io.Copy(pW, in)
select {
case <-ctx.Done():
// If the context was closed, p.closeWithError
// was already called. Calling it again would
// change the error that Read returns.
default:
p.closeWithError(err)
}
in.Close()
}()
go func() {
for {
select {
case <-ctx.Done():
p.closeWithError(ctx.Err())
case <-doneCtx.Done():
return
}
}
}()
return p
}
// Read wraps the Read method of the pipe that provides data from the wrapped
// ReadCloser.
func (p *cancelReadCloser) Read(buf []byte) (n int, err error) {
return p.pR.Read(buf)
}
// closeWithError closes the wrapper and its underlying reader. It will
// cause future calls to Read to return err.
func (p *cancelReadCloser) closeWithError(err error) {
p.pW.CloseWithError(err)
p.cancel()
}
// Close closes the wrapper its underlying reader. It will cause
// future calls to Read to return io.EOF.
func (p *cancelReadCloser) Close() error {
if !p.closed.CompareAndSwap(false, true) {
subsequentCloseWarn("cancelReadCloser")
return nil
}
p.closeWithError(io.EOF)
return nil
}
func subsequentCloseWarn(name string) {
logrus.Error("subsequent attempt to close " + name)
if logrus.GetLevel() >= logrus.DebugLevel {
logrus.Errorf("stack trace: %s", string(debug.Stack()))
}
}

@ -0,0 +1,11 @@
//go:build !windows
// +build !windows
package ioutils // import "github.com/docker/docker/pkg/ioutils"
import "os"
// TempDir on Unix systems is equivalent to os.MkdirTemp.
func TempDir(dir, prefix string) (string, error) {
return os.MkdirTemp(dir, prefix)
}

@ -0,0 +1,16 @@
package ioutils // import "github.com/docker/docker/pkg/ioutils"
import (
"os"
"github.com/docker/docker/pkg/longpath"
)
// TempDir is the equivalent of os.MkdirTemp, except that the result is in Windows longpath format.
func TempDir(dir, prefix string) (string, error) {
tempDir, err := os.MkdirTemp(dir, prefix)
if err != nil {
return "", err
}
return longpath.AddPrefix(tempDir), nil
}

@ -0,0 +1,92 @@
package ioutils // import "github.com/docker/docker/pkg/ioutils"
import (
"io"
"sync"
)
// WriteFlusher wraps the Write and Flush operation ensuring that every write
// is a flush. In addition, the Close method can be called to intercept
// Read/Write calls if the targets lifecycle has already ended.
type WriteFlusher struct {
w io.Writer
flusher flusher
flushed chan struct{}
flushedOnce sync.Once
closed chan struct{}
closeLock sync.Mutex
}
type flusher interface {
Flush()
}
var errWriteFlusherClosed = io.EOF
func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
select {
case <-wf.closed:
return 0, errWriteFlusherClosed
default:
}
n, err = wf.w.Write(b)
wf.Flush() // every write is a flush.
return n, err
}
// Flush the stream immediately.
func (wf *WriteFlusher) Flush() {
select {
case <-wf.closed:
return
default:
}
wf.flushedOnce.Do(func() {
close(wf.flushed)
})
wf.flusher.Flush()
}
// Flushed returns the state of flushed.
// If it's flushed, return true, or else it return false.
func (wf *WriteFlusher) Flushed() bool {
// BUG(stevvooe): Remove this method. Its use is inherently racy. Seems to
// be used to detect whether or a response code has been issued or not.
// Another hook should be used instead.
var flushed bool
select {
case <-wf.flushed:
flushed = true
default:
}
return flushed
}
// Close closes the write flusher, disallowing any further writes to the
// target. After the flusher is closed, all calls to write or flush will
// result in an error.
func (wf *WriteFlusher) Close() error {
wf.closeLock.Lock()
defer wf.closeLock.Unlock()
select {
case <-wf.closed:
return errWriteFlusherClosed
default:
close(wf.closed)
}
return nil
}
// NewWriteFlusher returns a new WriteFlusher.
func NewWriteFlusher(w io.Writer) *WriteFlusher {
var fl flusher
if f, ok := w.(flusher); ok {
fl = f
} else {
fl = &NopFlusher{}
}
return &WriteFlusher{w: w, flusher: fl, closed: make(chan struct{}), flushed: make(chan struct{})}
}

@ -0,0 +1,74 @@
package ioutils // import "github.com/docker/docker/pkg/ioutils"
import (
"io"
"sync/atomic"
)
// NopWriter represents a type which write operation is nop.
type NopWriter struct{}
func (*NopWriter) Write(buf []byte) (int, error) {
return len(buf), nil
}
type nopWriteCloser struct {
io.Writer
}
func (w *nopWriteCloser) Close() error { return nil }
// NopWriteCloser returns a nopWriteCloser.
func NopWriteCloser(w io.Writer) io.WriteCloser {
return &nopWriteCloser{w}
}
// NopFlusher represents a type which flush operation is nop.
type NopFlusher struct{}
// Flush is a nop operation.
func (f *NopFlusher) Flush() {}
type writeCloserWrapper struct {
io.Writer
closer func() error
closed atomic.Bool
}
func (r *writeCloserWrapper) Close() error {
if !r.closed.CompareAndSwap(false, true) {
subsequentCloseWarn("WriteCloserWrapper")
return nil
}
return r.closer()
}
// NewWriteCloserWrapper returns a new io.WriteCloser.
func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser {
return &writeCloserWrapper{
Writer: r,
closer: closer,
}
}
// WriteCounter wraps a concrete io.Writer and hold a count of the number
// of bytes written to the writer during a "session".
// This can be convenient when write return is masked
// (e.g., json.Encoder.Encode())
type WriteCounter struct {
Count int64
Writer io.Writer
}
// NewWriteCounter returns a new WriteCounter.
func NewWriteCounter(w io.Writer) *WriteCounter {
return &WriteCounter{
Writer: w,
}
}
func (wc *WriteCounter) Write(p []byte) (count int, err error) {
count, err = wc.Writer.Write(p)
wc.Count += int64(count)
return
}

@ -0,0 +1,26 @@
// longpath introduces some constants and helper functions for handling long paths
// in Windows, which are expected to be prepended with `\\?\` and followed by either
// a drive letter, a UNC server\share, or a volume identifier.
package longpath // import "github.com/docker/docker/pkg/longpath"
import (
"strings"
)
// Prefix is the longpath prefix for Windows file paths.
const Prefix = `\\?\`
// AddPrefix will add the Windows long path prefix to the path provided if
// it does not already have it.
func AddPrefix(path string) string {
if !strings.HasPrefix(path, Prefix) {
if strings.HasPrefix(path, `\\`) {
// This is a UNC path, so we need to add 'UNC' to the path as well.
path = Prefix + `UNC` + path[1:]
} else {
path = Prefix + path
}
}
return path
}

@ -14,7 +14,7 @@ github.com/cloudfoundry/jibber_jabber
# github.com/davecgh/go-spew v1.1.1
## explicit
github.com/davecgh/go-spew/spew
# github.com/docker/cli v20.10.15+incompatible
# github.com/docker/cli v23.0.10-0.20240125104041-672b1497b973+incompatible
## explicit
github.com/docker/cli/cli/config
github.com/docker/cli/cli/config/configfile
@ -51,6 +51,8 @@ github.com/docker/docker/api/types/volume
github.com/docker/docker/client
github.com/docker/docker/errdefs
github.com/docker/docker/pkg/homedir
github.com/docker/docker/pkg/ioutils
github.com/docker/docker/pkg/longpath
github.com/docker/docker/pkg/stdcopy
# github.com/docker/docker-credential-helpers v0.8.0
## explicit; go 1.19

Loading…
Cancel
Save